(defvar DOJO-INDENT-STATE-DEFAULT) (defvar DOJO-INDENT-STATE-REF-OPEN-CURLY-BRACKET-FOUND) (defvar DOJO-INDENT-STATE-REF-FCT-ROUND-CLOSE-BRACKET-FOUND) (defvar DOJO-INDENT-ROUND-SQUARE-BRACKET-LIMIT 100) ; State while indenting either a single line, or a whole region. ; ; The state is *not* shared between subsequent indentations ; triggered by the user; implementing this would be very difficult ; to senseless, since the underlying file may change in between ; (and usually is changed by the indentation itself). (cl-defstruct (dojo-js-indent-state (:constructor nil) (:constructor construct-dojo-js-indent-state (&optional state ref-open-curly-bracket-line indent-chars))) ; State attribute used e.g. while searching for the reference bracket of a multi-line function/if/etc. declaration. ; See the DOJO-INDENT-STATE attributes above. (state nil) ; While searching for the reference bracket of a multi-line function/if/etc. declaration, ; this is the line number of the opening curly bracket. (ref-open-curly-bracket-line nil) ; dojo-js-indent-char instances for all characters interesting for the current indentation. ; Sorted ascending by character position. ; While performing the indentation, we just move from character to character in this list, ; without needing to deal with comments, code, etc. in between. (indent-chars nil) ; dojo-js-indent-member instances cache information about desired indentation levels ; (at present of object member lines) that is already known, but needed lateron. ; E.g., we need to calculate all indentations of a whole object declaration anyway ; already for the very first line, so we can easily reuse them lateron, instead of ; performing the same calculation again. (line-to-member (make-hash-table :test 'equal)) ; The index in the indent-chars list. Here, the information where we currently are ; while processing the outer loop of a region indentation is stored. (indent-index 0) ; True if the file we indent looks like an i18n file. Then, e.g. trying to align the ; object members is senseless. (i18n-mode nil)) ; Information about a character relevant for indentation. (cl-defstruct (dojo-js-indent-char (:constructor nil) (:constructor construct-dojo-js-indent-char (&optional char line position-against-indentation position state))) (char nil) ; The character (line nil) ; Line of the character (position-against-indentation nil) ; Position of the character against indentation, i.e. the first non-whitespace character on the line (position nil) ; (point) of the character; lateron during indentation not reliable as indentation may change the file (state nil)) ; The state in the sense of dojo-js-indent-state-state ; Cached information about desired indentation of lines. Currently only used ; for indentation of object members. (cl-defstruct (dojo-js-indent-member (:constructor nil) (:constructor construct-dojo-js-indent-member (&optional width line))) (width nil) ; The number of characters between (a) the first non-blank character on the line at hand, and (b) the colon ':'. ; Used for determining how many characters to indent to align all object member lines at the colon. (line nil) ; Line number (reference-level nil) ; The reference level is the indentation enforced by the next-upper level of code (offset nil)) ; The offset is the additional indendation against that next-upper level of code. (defun dojo-js-indent-get-chars-string (indent-chars) "Returns a string representing the given list of indent-chars. Used for debug output." (let ((s "")) (dolist (indent-char indent-chars) (let* ((char (dojo-js-indent-char-char indent-char)) (line (dojo-js-indent-char-line indent-char)) (position-against-indentation (dojo-js-indent-char-position-against-indentation indent-char)) (position (dojo-js-indent-char-position indent-char))) (setq s (concat s (format "('%s',%s,%s,%s)" (if char (char-to-string char) "---") line position-against-indentation position))))) s)) (defun dojo-js-indent-looks-like-i18n-file () (string-match-p "nls/[[:alnum:]-]+\\.js$\\|nls/[a-z-]+/[[:alnum:]-]+\\.js$" (buffer-file-name))) (defun dojo-js-indent-indent-current-buffer (&optional arg) "If some region is selected, this function indents that region. If no region is selected, it indents, and untabifies, the whole buffer." (interactive) (if mark-active (indent-for-tab-command arg) (untabify (buffer-end -1) (buffer-end 1)) (indent-region (buffer-end -1) (buffer-end 1)))) (defun dojo-js-indent-region (start end) "Replacement for the usual indent-region function that collects necessary information once, and not once for every line." (interactive "r") (save-excursion (setq end (copy-marker end)) (goto-char start) (let* ((pr (unless (minibufferp) (make-progress-reporter "Indenting js region..." (point) end))) (indent-chars ()) (indent-state (construct-dojo-js-indent-state 'DOJO-INDENT-STATE-DEFAULT nil indent-chars))) (setf (dojo-js-indent-state-i18n-mode indent-state) (dojo-js-indent-looks-like-i18n-file)) (dojo-js-indent-derive-chars indent-state start end) (log-indent (format "Initial indent-chars are [%s]" (dojo-js-indent-get-chars-string (dojo-js-indent-state-indent-chars indent-state)))) (while (< (point) end) (or (and (bolp) (eolp)) (dojo-js-indent-do-indent-line indent-state)) (forward-line 1) (and pr (progress-reporter-update pr (point)))) (and pr (progress-reporter-done pr)) (move-marker end nil)))) (defun dojo-js-indent-line () "Replacement for the usual function indenting a single line. Sets up the necessary data, and then proceeds to the function actually doing the work." (let* ((line-start (line-beginning-position)) (line-end (line-end-position)) (line-indentation-point (dojo-js-indent-get-curr-line-indentation-point)) (pos-against-line (- (point) line-indentation-point)) (indent-chars ()) (indent-state (construct-dojo-js-indent-state 'DOJO-INDENT-STATE-DEFAULT nil indent-chars))) (setf (dojo-js-indent-state-i18n-mode indent-state) (dojo-js-indent-looks-like-i18n-file)) ; Derive information about characters relevant for indentation. (dojo-js-indent-derive-chars indent-state line-start line-end) (log-indent (format "Initial indent-chars are [%s]" (dojo-js-indent-get-chars-string (dojo-js-indent-state-indent-chars indent-state)))) (dojo-js-indent-do-indent-line indent-state) (log-indent (format "line-start [%s], line-end [%s], curr-line-indentation-point is [%s]" line-start line-end (dojo-js-indent-get-curr-line-indentation-point))) (log-indent (format "line-indentation-point [%s], pos-against-line is [%s]" line-indentation-point pos-against-line)) (let ((new-indentation-point (dojo-js-indent-get-curr-line-indentation-point))) (if (< pos-against-line 0) (goto-char new-indentation-point) (goto-char (+ new-indentation-point pos-against-line)))))) (defun dojo-js-indent-do-indent-line (indent-state) "Indents the line at point, based on the given indent-state. The indent-state at this point especially needs to contain the indent-chars set up by dojo-js-indent-derive-chars." (let* ((line (line-number-at-pos)) (indent-chars (dojo-js-indent-state-indent-chars indent-state)) (indent-index (dojo-js-indent-state-indent-index indent-state)) (initial-trigger-indent-index nil) (trigger-indent-index nil)) ; Starting at the current indent-index (which might be set to a non-zero value ; as result of indenting the previous line of a indent-region), proceed until we ; are at a indent-index corresponding to the current line to indent. (while (and (< indent-index (length indent-chars)) (< (dojo-js-indent-char-line (nth indent-index indent-chars)) line)) (incf indent-index)) (if (<= indent-index 0) (progn (log-indent (format "No interesting character before point, indenting to level zero.")) (indent-line-to 0)) ; Step to the last indent-char before the current line to indent (decf indent-index) (log-indent (format "Initial indent index is [%s], for line [%s]" indent-index line)) (let* ((line-to-member (dojo-js-indent-state-line-to-member indent-state)) (member (gethash line line-to-member))) (if (not (null member)) ; For determining the indentation of object declarations (where we align ; at the colons), we have to inspect the whole object. Thus, we know ; the indentations of all member lines when indenting the very first ; one. Instead of doing the same calculation multiple times, we ; cache the result and use it here. (let* ((reference-level (dojo-js-indent-member-reference-level member)) (offset (dojo-js-indent-member-offset member)) (desired-indentation (+ reference-level offset))) (indent-line-to desired-indentation) (log-indent (format "Indenting line [%s] to indentation [%s] = [%s] + [%s] based on object member cache." line desired-indentation reference-level offset))) ; Step back to the character triggering indentation (e.g. the starting '{' of a code block) (setq initial-trigger-indent-index (if (null indent-index) nil (dojo-js-indent-get-initial-trigger-indent-index indent-index indent-chars))) (setq trigger-indent-index (if (null initial-trigger-indent-index) nil (dojo-js-indent-get-trigger-indent-index indent-index initial-trigger-indent-index indent-chars))) (let* ((trigger-indent-char (if (null trigger-indent-index) nil (nth trigger-indent-index indent-chars))) (trigger-position (if trigger-indent-char (dojo-js-indent-char-position trigger-indent-char) "---"))) (log-indent (format "Initial trigger index [%s], trigger indent index [%s] at position [%s]" initial-trigger-indent-index trigger-indent-index trigger-position))) (if (null trigger-indent-index) (progn (log-indent (format "No trigger-indent-index found, indenting to level zero.")) (indent-line-to 0)) ; Actually do the calculation (let* ((trigger-indent-char (nth trigger-indent-index indent-chars)) (reference-level (dojo-js-indent-get-line-indentation trigger-indent-char)) (offset (dojo-js-indent-get-offset indent-state reference-level indent-chars indent-index initial-trigger-indent-index trigger-indent-index)) (desired-indentation (+ reference-level offset))) (log-indent (format "Indenting line [%s] to indentation [%s] = [%s] + [%s]; point is now [%s]" line desired-indentation reference-level offset (point))) (indent-line-to desired-indentation)))))) (setf (dojo-js-indent-state-indent-index indent-state) indent-index))) (defun dojo-js-indent-get-offset (indent-state reference-level indent-chars indent-index initial-trigger-indent-index trigger-indent-index) ;"Calculates and returns the offset a line should have against the ;line triggering indentation (which is e.g. the line containing the ;'{' of a block). ;Special cases involve ;- the initial define-call (to prevent it from senseless additional levels of indentation) ;- object declarations which we align at the colons ':' ;- the final closing brackets of blocks ;If no special cases apply, the configured dojo-indent-with is used." (let* ((trigger-indent-char (nth trigger-indent-index indent-chars)) (trigger-char (dojo-js-indent-char-char trigger-indent-char)) (trigger-line (dojo-js-indent-char-line trigger-indent-char)) (trigger-position-against-indentation (dojo-js-indent-char-position-against-indentation trigger-indent-char)) (trigger-line-string (dojo-common-strings-get-trimmed-line trigger-line)) (initial-trigger-indent-char (nth initial-trigger-indent-index indent-chars)) (initial-trigger-char (dojo-js-indent-char-char initial-trigger-indent-char)) (indent-char (nth indent-index indent-chars)) (indent-line-string (dojo-common-strings-trim (thing-at-point 'line t)))) (cond ((and (= reference-level 0) (starts-with trigger-line-string "define(") (starts-with indent-line-string "function(")) (log-indent (format "Using offset [0] because of define function rule.")) 0) ; If we indent inside some function definition or call, some if/while/for/etc. statement ; we usually indent at the column of the corresponding opening bracket. This way, e.g. in a function ; call, all parameters start at the same column, and one doesn't need to search the subsequent parameters ; somewhere far left. ; See below for cases where we don't apply that logic. ((= initial-trigger-char ?\() (let* ((trigger-line-indentation (dojo-js-indent-get-line-indentation trigger-indent-char)) (trigger-char-column (+ trigger-line-indentation trigger-position-against-indentation))) (log-indent (format "trigger-line-indentation [%s], pos-against-indentation [%s], trigger-char-column [%s], ref-level [%s]" trigger-line-indentation trigger-position-against-indentation trigger-char-column reference-level)) (if (and ; Indent at the left by dojo-indent-width, if we are too far at the right, to avoid things being placed in a ; small region at the far right of the screen. (< trigger-char-column DOJO-INDENT-ROUND-SQUARE-BRACKET-LIMIT) ; If the to-be-indented-line contains the string "function", we most probably have some function definition ; as parameter, possibly as part of an asyncronous handler. Then it's usually senseful to align them as ; left as possible, as inside the function, we have to expect long lines as well. ; NOTE: This check is quite heuristic, however finding out about a function definition in a safe manner ; would be much more complicate. (null (string-match-p "function" indent-line-string))) (1+ (- trigger-char-column reference-level)) dojo-indent-width))) ((= initial-trigger-char ?\[) (let* ((trigger-line-indentation (dojo-js-indent-get-line-indentation trigger-indent-char)) (trigger-char-column (+ trigger-line-indentation trigger-position-against-indentation))) (log-indent (format "trigger-line-indentation [%s], pos-against-indentation [%s], trigger-char-column [%s], ref-level [%s]" trigger-line-indentation trigger-position-against-indentation trigger-char-column reference-level)) (cond ((starts-with indent-line-string "]") 0) ; Indent at the left by dojo-indent-width, if we are too far at the right, to avoid things being placed in a ; small region at the far right of the screen. ((< trigger-char-column DOJO-INDENT-ROUND-SQUARE-BRACKET-LIMIT) (1+ (- trigger-char-column reference-level))) (t dojo-indent-width)))) ; Starting at this point, we talk about indentation against '{' ((and (not (dojo-js-indent-state-i18n-mode indent-state)) (dojo-js-indent-looks-like-object-member-line (point)) (dojo-js-indent-do-object-indentation reference-level trigger-indent-char)) (dojo-js-indent-get-object-member-offset indent-state reference-level trigger-line)) ((or (starts-with indent-line-string "}") (starts-with indent-line-string "]") (starts-with indent-line-string ")")) 0) ; A '?' at the beginning of a line suggests that we are inside a 'foo = (a ? b : c)' statement. ; If we find in the line before a '=', align at it, otherwise just do a usual indentation. ((or (starts-with indent-line-string "?")) (let* ((before-line (dojo-js-indent-get-before-line (point))) (assignment-index (if before-line (string-match-p "=" before-line) nil))) (if (null assignment-index) dojo-indent-width (- assignment-index reference-level)))) ; Similar case: A ':' at the beginning of a line also suggests a 'foo = (a ? b : c)' statement. ((or (starts-with indent-line-string ":")) (let* ((before-line (dojo-js-indent-get-before-line (point))) (assignment-index (if before-line (string-match-p "=\\|?" before-line) nil))) (if (null assignment-index) dojo-indent-width (- assignment-index reference-level)))) ((eq (string-match-p "\\+\\|-\\|/^/\\|\\*" indent-line-string) 0) (let* ((before-line (dojo-js-indent-get-before-line (point))) (assignment-index (if before-line (string-match-p "=\\|\\*\\|/\\|-\\|\\+" before-line) nil))) (if (null assignment-index) dojo-indent-width (- assignment-index reference-level)))) (t (log-indent (format "Using default offset [%s]" dojo-indent-width)) dojo-indent-width)))) (defun dojo-js-indent-get-object-member-offset (indent-state reference-level trigger-line) "Calculates the desired indentation (precisely: the offset part as returned by dojo-js-indent-get-offset) for the object member line point is currently located at. Calculates desired indentations for all members of the corresponding object declaration, and saves them in dojo-js-indent-state-line-to-member. This way, when indenting a whole region, this calculation needs to run just once, for lateron member lines, the desired indentation level can be fetced from line-to-member." (log-indent (format "Calculating member offset with reference-level [%s] and trigger-line [%s]" reference-level trigger-line)) (save-excursion ; Calculate information about all object members (let* ((members (save-excursion (goto-line trigger-line) (move-end-of-line nil) (dojo-js-indent-step-forward))) (max-width nil) (colon-offset nil) (line-to-indent (line-number-at-pos)) (line-to-member (dojo-js-indent-state-line-to-member indent-state)) (result nil)) ; Derive maximum width of object member names (dolist (member members) (let* ((width (dojo-js-indent-member-width member))) (if (or (null max-width) (> width max-width)) (setq max-width width)))) ; Calculate the offset of the object member declaration ; colons (we align the member declarations such that ; all colons are in the same column) (setq colon-offset (if (null max-width) 0 (+ dojo-indent-width max-width))) ; Register the desired indentation for all member lines ; in line-to-member, and derive the offset for the line ; we currently indent. (dolist (member members) (let* ((width (dojo-js-indent-member-width member)) (line (dojo-js-indent-member-line member)) (offset (- colon-offset width))) (setf (dojo-js-indent-member-reference-level member) reference-level) (setf (dojo-js-indent-member-offset member) offset) (puthash line member line-to-member) (if (= line-to-indent line) (setq result offset)))) (log-indent (format "Derived member offset [%s] for line [%s] based on members [%s]" result line-to-indent (dojo-js-indent-get-object-member-string members))) ; Return that offset, or a default value if something went surprisingly wrong (if (null result) dojo-indent-width result)))) (defun dojo-js-indent-get-object-member-string (members) "Returns a string containing information about the given dojo-js-indent-member list. Used for debugging." (let ((s "")) (dolist (member members) (let* ((width (dojo-js-indent-member-width member)) (line (dojo-js-indent-member-line member))) (setq s (concat s (format "(%s,%s)" line width))))) s)) (defun dojo-js-indent-get-before-line (position) "Returns the line before point as a string, using thing-at-point 'line." (save-excursion (goto-char position) (if (= (line-number-at-pos) 0) nil (forward-line -1) (thing-at-point 'line)))) (defun dojo-js-indent-derive-chars (indent-state start end) "Derives the list of dojo-js-indent-chars for the given region, and saves it in the given dojo-indent-state. That list contains information about characters interesting for indentation (i.e., the brackets). Comments will be ignored when setting up the list, i.e. brackets found within a comment will *not* disturb that list. The code can (and often will) extend the region especially towards the top, in order to find out about the reference characters (e.g., the '{' to indent against in a block of code). Note that we do derive some additional information on the fly e.g. when calculating the offset of a particular line, to avoid overflooding that code with too many characters to deal with. E.g., we try to indent a '?' against a potential '=' in the previous line. Such code should generally work stable as long as one avoids placing e.g. comments at surprising locations like at the beginning of a line, followed by code." (save-excursion (goto-char end) (dojo-js-indent-step-backwards (point) 'dojo-js-indent-make-indentation-loop-prev-chartable 'dojo-js-indent-make-indentation-loop-level-chartable 'dojo-js-indent-process-indent-char 'dojo-js-indent-indentation-update-prev-char-table 'dojo-is-indent-log-step-backwards-loop start nil indent-state))) (defun dojo-js-indent-step-backwards (orig-point prev-chartable-constructor-fct level-chartable-constructor-fct process-char-fct update-prev-char-table-fct step-backward-log-fct region-start limit indent-state) (log-indent (format "Called dojo-js-indent-step-backwards with region-start [%s], point [%s] and limit [%s]" region-start (point) limit)) (let* ((stop nil) (limit-reached nil) (region-start-passed nil) (level-char-table (funcall level-chartable-constructor-fct)) (prev-ignore-block-end nil) (prev-line-comment-start (dojo-js-indent-get-prev-line-comment-start)) (prev-line-comment-end (dojo-js-indent-get-prev-line-comment-end prev-line-comment-start)) (prev-block-comment-end (dojo-js-indent-get-prev-block-comment-end)) (prev-block-comment-start (dojo-js-indent-get-prev-block-comment-start prev-block-comment-end)) (raw-prev-single-quote (dojo-js-indent-get-prev-single-quote)) (raw-prev-double-quote (dojo-js-indent-get-prev-double-quote)) (prev-single-quote (dojo-js-indent-get-prev-quote "'" prev-line-comment-end prev-block-comment-start prev-block-comment-end raw-prev-single-quote raw-prev-double-quote)) (prev-double-quote (dojo-js-indent-get-prev-quote "\"" prev-line-comment-end prev-block-comment-start prev-block-comment-end raw-prev-double-quote raw-prev-single-quote)) (prev-ignore-block-end (dojo-js-indent-get-prev-ignore-block-end prev-single-quote prev-double-quote prev-line-comment-start prev-line-comment-end prev-block-comment-start prev-block-comment-end limit)) (prev-char-table (funcall prev-chartable-constructor-fct prev-ignore-block-end)) (iteration 0)) (while (and (null stop) (null limit-reached)) ; (funcall step-backward-log-fct iteration prev-ignore-block-end level-char-table prev-char-table) ; (log-indent (format "... Ignore-blocks: //=%s, EOL=%s, /*=%s, */=%s, '=%s, \"=%s" prev-line-comment-start prev-line-comment-end ; prev-block-comment-start prev-block-comment-end prev-single-quote prev-double-quote)) ; Assumption: All the prev-* variables contain positions before point. (let* ((last-token (dojo-common-math-char-table-max-with-identifier prev-char-table)) (last-char (if (null last-token) nil (nth 0 last-token))) (last-pos (if (null last-token) nil (nth 1 last-token))) (ignore-block-start nil)) ; Only set if we decide to jump over the previous ignore block ; If there is an interesting character before point, but after prev-ignore-block-end, move point there. (if (not (null last-char)) (log-indent (format "Iteration [%s]: Last-char [%s] at pos [%s]" iteration (char-to-string last-char) last-pos))) (if (not (null last-pos)) (goto-char last-pos)) (cond ((null last-char) ; If there is no further interesting character between the current position, and the end of the previous ; ignore block, we need to step backwards over that ignore block. (cond ((= prev-ignore-block-end 0) (log-indent (format "Stopping because of lack of both previous interesting characters, and of previous ignore blocks.")) (setq stop t)) ((and (not (null limit)) (<= prev-ignore-block-end limit)) (log-indent (format "Limit [%s] reached" limit)) (setq limit-reached t)) (t (goto-char prev-ignore-block-end) (cond ((eq prev-ignore-block-end prev-single-quote) (setq ignore-block-start (dojo-js-indent-jump-to-string-literal-start "'")) ; (log-indent (format "... Found ignore-block-start [%s] based on '" ignore-block-start)) ) ((eq prev-ignore-block-end prev-double-quote) (setq ignore-block-start (dojo-js-indent-jump-to-string-literal-start "\"")) ; (log-indent (format "... Found ignore-block-start [%s] based on \"" ignore-block-start)) ) ((eq prev-ignore-block-end prev-line-comment-end) (setq ignore-block-start (dojo-js-indent-jump-to-line-comment-start)) ; (log-indent (format "... Found ignore-block-start [%s] based on //" ignore-block-start)) ) ((eq prev-ignore-block-end prev-block-comment-end) (setq ignore-block-start (dojo-js-indent-jump-to-block-comment-start)) ; (log-indent (format "... Found ignore-block-start [%s] based on /*" ignore-block-start)) ) (t (log-indent (format "Stop because of unrecognized prev-ignore-block-end when trying to step to ignore-block-start at pos [%s]" prev-ignore-block-end)) (setq stop t)))))) ; Set to zero indentation, to avoid triggering an endless loop if a bug really triggers this casbe ; If we found an interesting character, adapt the stored information (e.g. nesting level of brackets), ; check wether we are able to decide about the desired indentation level, or search the previous ; occurrence of the interesting character else. (t (let* ((old-region-start-passed region-start-passed)) (setq region-start-passed (< (point) region-start)) ; (log-indent (format "region-start = [%s], region-start-passed = [%s], old-region-start-passed = [%s], (point)=[%s]" ; region-start region-start-passed old-region-start-passed (point))) (setq stop (funcall process-char-fct orig-point last-char last-pos prev-ignore-block-end region-start-passed (and (not old-region-start-passed) region-start-passed) prev-char-table level-char-table indent-state))) (log-indent (format "indent-chars are [%s]" (dojo-js-indent-get-chars-string indent-chars))) ) ) (if (and (null stop) (null limit-reached)) (if (null ignore-block-start) ; If ignore-block-start is nil, we found an interesting character after the ignore block, and did not step backwards over it. ; Thus, we jump to that character, and proceed backwards from that point. (goto-char last-pos) ; Else, we jumped to the start of the ignore block above, and now have to update the information about other ; interesting characters and ignore blocks before point. (if (and (not (null prev-line-comment-end)) (>= prev-line-comment-end ignore-block-start)) (progn (setq prev-line-comment-start (dojo-js-indent-get-prev-line-comment-start)) (setq prev-line-comment-end (dojo-js-indent-get-prev-line-comment-end prev-line-comment-start)))) (if (and (not (null prev-block-comment-end)) (>= prev-block-comment-end ignore-block-start)) (progn (setq prev-block-comment-end (dojo-js-indent-get-prev-block-comment-end)) (setq prev-block-comment-start (dojo-js-indent-get-prev-block-comment-start prev-block-comment-end)))) (if (and (not (null prev-single-quote)) (>= prev-single-quote ignore-block-start)) (setq prev-single-quote (dojo-js-indent-get-prev-single-quote))) (if (and (not (null prev-double-quote)) (>= prev-double-quote ignore-block-start)) (setq prev-double-quote (dojo-js-indent-get-prev-double-quote))) ; Update the prev-ignore-block-end. Don't allow it to ever increase. ; Without applying the min operator below, for a line like ; // // some comment ; after processing the second '//', point is at that '//', and then we usually would ; search for interesting characters in the region between point and the previous ; ignore block end. Unfortunately, the first '//' also spreads until end-of-line. ; Thus, set prev-ignore-block-end to point if point is before, to avoid search-backward with ; limit after point, which triggers an error. ; Also set the line/block-comment-end if appropriate, to avoid disturbing the code near ; "Unrecognized prev-ignore-block-end". (setq prev-ignore-block-end (dojo-js-indent-get-prev-ignore-block-end prev-single-quote prev-double-quote prev-line-comment-start prev-line-comment-end prev-block-comment-start prev-block-comment-end limit)) (if (> prev-ignore-block-end (point)) (progn (if (eq prev-ignore-block-end prev-line-comment-end) (setq prev-line-comment-end (point))) (if (eq prev-ignore-block-end prev-block-comment-end) (setq prev-block-comment-end (point))) (setq prev-ignore-block-end (point)))) (funcall update-prev-char-table-fct prev-char-table prev-ignore-block-end))) (incf iteration) )) (if limit-reached (progn (goto-char limit))))) (defun dojo-js-indent-make-indentation-loop-prev-chartable (prev-ignore-block-end) (let* ((char-table (make-char-table 'indentation-loop-prev-chartable))) (set-char-table-range char-table ?\( (dojo-js-indent-get-prev-round-bracket-open prev-ignore-block-end)) (set-char-table-range char-table ?\) (dojo-js-indent-get-prev-round-bracket-close prev-ignore-block-end)) (set-char-table-range char-table ?\[ (dojo-js-indent-get-prev-square-bracket-open prev-ignore-block-end)) (set-char-table-range char-table ?\] (dojo-js-indent-get-prev-square-bracket-close prev-ignore-block-end)) (set-char-table-range char-table ?\{ (dojo-js-indent-get-prev-curly-bracket-open prev-ignore-block-end)) (set-char-table-range char-table ?\} (dojo-js-indent-get-prev-curly-bracket-close prev-ignore-block-end)) char-table)) (defun dojo-js-indent-make-indentation-loop-level-chartable () (let* ((char-table (make-char-table 'indentation-loop-level-chartable))) (set-char-table-range char-table ?\( 0) (set-char-table-range char-table ?\[ 0) (set-char-table-range char-table ?\{ 0) char-table)) (defun dojo-js-indent-process-indent-char (orig-point last-char last-pos prev-ignore-block-end region-start-passed region-start-just-passed prev-char-table level-char-table indent-state) ; (log-indent (format "indentation-loop-process-char processes char [%s] at [%s]" (char-to-string last-char) last-pos)) (let* ((indent-chars (dojo-js-indent-state-indent-chars indent-state)) (state (dojo-js-indent-state-state indent-state)) (line (line-number-at-pos last-pos)) (line-start-pos (dojo-js-indent-get-line-indentation-point line)) (stop nil)) (if region-start-just-passed (progn ; Once we passed the region-start (= the start of the region we want to indent), we need to ; determine the highest bracket level inside that region, relative to its start. ; Reason: From the start of the region, we step further backwards until we got up one further ; level of brackets; e.g. from a line inside a block '{ ... }' towards the initial '{' of ; the block. However, our region might look like '... } { ... '. Then using that approach, ; we step further back until the '{' before that region. But this is wrong for the space ; around '} {', which in fact needs to be indented against the '{' one level further up. (let* ((max-round 0) (max-square 0) (max-curly 0)) (set-char-table-range level-char-table ?\( 0) (set-char-table-range level-char-table ?\[ 0) (set-char-table-range level-char-table ?\{ 0) (dolist (indent-char indent-chars) (let* ((char (dojo-js-indent-char-char indent-char))) (cond ((= char ?\() (dojo-common-math-add-to-char-table-entry level-char-table ?\( -1)) ((= char ?\)) (dojo-common-math-add-to-char-table-entry level-char-table ?\( 1)) ((= char ?\[) (dojo-common-math-add-to-char-table-entry level-char-table ?\[ -1)) ((= char ?\]) (dojo-common-math-add-to-char-table-entry level-char-table ?\[ 1)) ((= char ?\{) (dojo-common-math-add-to-char-table-entry level-char-table ?\{ -1)) ((= char ?\}) (dojo-common-math-add-to-char-table-entry level-char-table ?\{ 1)) (t ())) (if (> (char-table-range level-char-table ?\() max-round) (setq max-round (char-table-range level-char-table ?\())) (if (> (char-table-range level-char-table ?\[) max-round) (setq max-square (char-table-range level-char-table ?\[))) (if (> (char-table-range level-char-table ?\{) max-round) (setq max-curly (char-table-range level-char-table ?\{))))) (set-char-table-range level-char-table ?\( max-round) (set-char-table-range level-char-table ?\[ max-square) (set-char-table-range level-char-table ?\{ max-curly) (log-indent (format "... Region start just passed, calculated levels (=%s, [=%s and {=%s to climb up." max-round max-square max-curly))))) (cond ((= last-char ?\() (if region-start-passed (dojo-common-math-add-to-char-table-entry level-char-table ?\( -1)) (set-char-table-range prev-char-table ?\( (save-excursion (search-backward "(" prev-ignore-block-end t)))) ((= last-char ?\)) (if region-start-passed (cond ((eq state 'DOJO-INDENT-STATE-REF-OPEN-CURLY-BRACKET-FOUND) ; This is the case, that we process something like 'function foo(param1, param2, param3) {', ; already have passed the '{', and now found the ')' before the '{'. ; We definitely want to stop at the corresponding '(', and thus in this special case do not ; increase the round-bracket level, but set it to zero. ; This way, we don't need to do anything special about this case lateron, but can just wait ; until the abort regularily because the pass the corresponding '('. (log-indent (format "Switching to state REF-FCT-ROUND-CLOSE-BRACKET-FOUND at ')'")) (setq state 'DOJO-INDENT-STATE-REF-FCT-ROUND-CLOSE-BRACKET-FOUND) (set-char-table-range level-char-table ?\( 0)) (t (dojo-common-math-add-to-char-table-entry level-char-table ?\( +1)))) (set-char-table-range prev-char-table ?\) (save-excursion (search-backward ")" prev-ignore-block-end t)))) ((= last-char ?\[) (if region-start-passed (dojo-common-math-add-to-char-table-entry level-char-table ?\[ -1)) (set-char-table-range prev-char-table ?\[ (save-excursion (search-backward "[" prev-ignore-block-end t)))) ((= last-char ?\]) (if region-start-passed (dojo-common-math-add-to-char-table-entry level-char-table ?\[ +1)) (set-char-table-range prev-char-table ?\] (save-excursion (search-backward "]" prev-ignore-block-end t)))) ((= last-char ?\{) (if region-start-passed (dojo-common-math-add-to-char-table-entry level-char-table ?\{ -1)) (set-char-table-range prev-char-table ?\{ (save-excursion (search-backward "{" prev-ignore-block-end t))) (if (< (char-table-range level-char-table ?\{) 0) (if (eq state 'DOJO-INDENT-STATE-DEFAULT) (progn (setq state 'DOJO-INDENT-STATE-REF-OPEN-CURLY-BRACKET-FOUND) (log-indent (format "Switching to state REF-OPEN-CURLY-BRACKET-FOUND at '{'")) (setf (dojo-js-indent-state-ref-open-curly-bracket-line indent-state) line))))) ; (< (char-table-range level-char-table ?\{) 0)) ((= last-char ?\}) (if region-start-passed (dojo-common-math-add-to-char-table-entry level-char-table ?\{ +1)) (set-char-table-range prev-char-table ?\} (save-excursion (search-backward "}" prev-ignore-block-end t)))) (t (error (format "Function dojo-js-indent-process-indent-char found unsupported char [%s] at pos [%s]" (char-to-string last-char) last-pos)))) (let* ((round-level (char-table-range level-char-table ?\()) (square-level (char-table-range level-char-table ?\[)) (curly-level (char-table-range level-char-table ?\{)) (ref-open-curly-bracket-line (dojo-js-indent-state-ref-open-curly-bracket-line indent-state))) (cond ((< round-level 0) (setq stop t)) ((< square-level 0) (setq stop t)) ((< curly-level 0) (cond ((eq state 'DOJO-INDENT-STATE-REF-OPEN-CURLY-BRACKET-FOUND) (setq stop (< line ref-open-curly-bracket-line))))))) (push (construct-dojo-js-indent-char last-char (line-number-at-pos last-pos) (- last-pos line-start-pos) last-pos state) indent-chars) (setf (dojo-js-indent-state-indent-chars indent-state) indent-chars) (setf (dojo-js-indent-state-state indent-state) state) stop)) (defun dojo-js-indent-indentation-update-prev-char-table (prev-char-table prev-ignore-block-end) (set-char-table-range prev-char-table ?\( (dojo-js-indent-get-prev-round-bracket-open prev-ignore-block-end)) (set-char-table-range prev-char-table ?\) (dojo-js-indent-get-prev-round-bracket-close prev-ignore-block-end)) (set-char-table-range prev-char-table ?\[ (dojo-js-indent-get-prev-square-bracket-open prev-ignore-block-end)) (set-char-table-range prev-char-table ?\] (dojo-js-indent-get-prev-square-bracket-close prev-ignore-block-end)) (set-char-table-range prev-char-table ?\{ (dojo-js-indent-get-prev-curly-bracket-open prev-ignore-block-end)) (set-char-table-range prev-char-table ?\} (dojo-js-indent-get-prev-curly-bracket-close prev-ignore-block-end))) (defun dojo-is-indent-log-step-backwards-loop (iteration prev-ignore-block-end level-char-table prev-char-table) (log-indent (format "Iteration %s: Point=[%s], Levels (=%s, [=%s, {=%s; prev-ignore-block-end [%s]" iteration (point) (char-table-range level-char-table ?\() (char-table-range level-char-table ?\[) (char-table-range level-char-table ?\{) prev-ignore-block-end)) (log-indent (format "... Characters: (=%s, )=%s, [=%s, ]=%s, {=%s, }=%s" (char-table-range prev-char-table ?\() (char-table-range prev-char-table ?\)) (char-table-range prev-char-table ?\[) (char-table-range prev-char-table ?\]) (char-table-range prev-char-table ?\{) (char-table-range prev-char-table ?\})))) ;(defun dojo-js-indent-correct-open-quotes (prev-) ; (progn ; (let* ((bound (if (null prev-quote) nil (save-excursion (goto-char prev-quote) (beginning-of-line) (point)))) ; (prev-prev-quote (cond ((null prev-quote) ; nil) ; ((and (not (null prev-single-quote)) (= prev-quote prev-single-quote)) ; (dojo-js-indent-get-prev-single-quote bound)) ; ((and (not (null prev-double-quote)) (= prev-quote prev-double-quote)) ; (dojo-js-indent-get-prev-double-quote bound))))) ; ))) (defun dojo-js-indent-get-prev-ignore-block-end (prev-single-quote prev-double-quote prev-line-comment-start prev-line-comment-end prev-block-comment-start prev-block-comment-end limit) "Given the locations of the last occurrences of single and double quotes, and of line/block comment starts/ends, before point, this function determines the end of the last block to be ignored before point. Important precondition: The position based on which these positions were determined is located outside any String literal, i.e. usually at the end of a line. The background of that precondition is, that otherwise we would actually have to inspect the whole file to determine wether we have a single/double quote inside a comment, or a comment inside a string literal." ; (log-indent (format "... Ignore-blocks for get-prev-ignore-block-end: //=%s, EOL=%s, /*=%s, */=%s, '=%s, \"=%s" prev-line-comment-start prev-line-comment-end ; prev-block-comment-start prev-block-comment-end prev-single-quote prev-double-quote)) (setq prev-ignore-block-end nil) ; Inspect comments and string literals. The prev-ignore-block-end is set ; to the end of the previous string literal or comment, whichever comes ; last but before point. (let* ((prev-quote nil) (prev-ignore-block-end)) ; Determine max(prev-single-quote, prev-double-quote) in a null-safe manner. (if (dojo-js-indent-greater-p prev-single-quote prev-quote) (setq prev-quote prev-single-quote)) (if (dojo-js-indent-greater-p prev-double-quote prev-quote) (setq prev-quote prev-double-quote)) (if (dojo-js-indent-greater-p prev-quote prev-line-comment-end) (if (dojo-js-indent-greater-p prev-quote prev-block-comment-end) (setq prev-ignore-block-end prev-quote) ; Previous quote after both end of line and end of block comment. (setq prev-ignore-block-end prev-block-comment-end)) ; Order EOL " */ ==> The */ is neither in a line comment, nor in a quotation (if (dojo-js-indent-greater-p prev-block-comment-end prev-line-comment-end) (setq prev-ignore-block-end prev-block-comment-end) ; Order " EOL */ ==> The */ is neither in a line comment, nor in a quotation (if (dojo-js-indent-greater-p prev-line-comment-start prev-block-comment-end) (setq prev-ignore-block-end prev-line-comment-end) ; Order /* */ // EOL ==> The complete line comment is after the block comment (if (dojo-js-indent-greater-p prev-line-comment-start prev-block-comment-start) ; Order // */ EOL (setq prev-ignore-block-end prev-block-comment-end) ; Order /* // */ EOL ==> The line comment is outcommented, thus the block comment counts (setq prev-ignore-block-end prev-line-comment-end))))) ; Case // /* */ EOL ==> The block comment is outcommented, thus the line comment counts ; If there is no previous ignore block, inspect everything starting with point zero. (if (null prev-ignore-block-end) (setq prev-ignore-block-end 0)) (if (and (not (null limit)) (< prev-ignore-block-end limit)) (setq prev-ignore-block-end limit)) prev-ignore-block-end) ) (defun dojo-js-indent-get-prev-line-comment-start () (save-excursion (search-backward "//" nil t)) ) (defun dojo-js-indent-get-prev-line-comment-end (prev-line-comment-start) (if (null prev-line-comment-start) nil (dojo-common-source-get-line-end prev-line-comment-start)) ) (defun dojo-js-indent-get-prev-block-comment-end () (save-excursion (search-backward "*/" nil t)) ) (defun dojo-js-indent-get-prev-block-comment-start (prev-block-comment-end) (if (null prev-block-comment-end) nil (save-excursion (goto-char prev-block-comment-end) (search-backward "/*" nil t))) ) (defun dojo-js-indent-get-prev-quote (our-quote prev-line-comment-end prev-block-comment-start prev-block-comment-end prev-our-quote prev-other-quote) "When we start jumping backwards to analyze where we are in the file, and what this means for the desired indentation, among other things we need to know about the position of string literals. In a well-formed js file, we don't expect multi-line string literals. Unfortunately, during typing, it can happen that we want to indent a line with a open string literal, something like 'return \"foo' Then we need to assume that that string literal is closed at the line end, since otherwise, we would regard code as being placed inside string literals, and vice versa. The purpose of this function is to detect that case, and return a maybe corrected position for the last quote before point. I.e., if the last token before point we recognize is a string literal, and the number of quotes on the line at hand is odd, we assume the case described above, and return (point) instead of prev-our-quote. NOTE: This function is heuristic in that, one can nest string literals and comments in both directions. A clean detection wether we have a quote inside a comment, or a comment start inside a string literal would only be possible if we would scan the whole file until point. For reasons of performance, we don't do this, and instead live with some room for unexpected indentation in corner cases." (let* ((line-start (save-excursion (beginning-of-line) (point)))) ; Check wether the given quote position is after all other ; relevant positions. (if (and (dojo-js-indent-greater-p prev-our-quote line-start) (dojo-js-indent-greater-p prev-our-quote prev-line-comment-end) (dojo-js-indent-greater-p prev-our-quote prev-block-comment-start) (dojo-js-indent-greater-p prev-our-quote prev-block-comment-end) (dojo-js-indent-greater-p prev-our-quote prev-other-quote)) (let* ((our-quote-pos prev-our-quote) (our-quote-count 1)) ; Count how many quotes of the expected type we have before point on the current line. (save-excursion (goto-char our-quote-pos) (while (not (null our-quote-pos)) ; (log-indent (format "our-quote-pos is [%s], point [%s], line-start [%s]" our-quote-pos (point) line-start)) (setq our-quote-pos (search-backward our-quote line-start t)) (if (not (null our-quote-pos)) (incf our-quote-count)))) ; (log-indent (format "get-prev-quote for our-quote [%s] derived our-quote-count [%s], will return maybe adapted prev-our-quote accordingly." ; our-quote our-quote-count)) (if (= (% our-quote-count 2) 1) (point) prev-our-quote)) ; (log-indent (format "get-prev-quote for our-quote [%s] returns unchanged prev-our-quote [%s], since the quote isn't the last char found." ; our-quote prev-our-quote)) prev-our-quote))) (defun dojo-js-indent-get-prev-single-quote (&optional bound) (save-excursion (search-backward "'" bound t)) ) (defun dojo-js-indent-get-prev-double-quote (&optional bound) (save-excursion (search-backward "\"" bound t)) ) ; TODO: Parameter prev-ignore-block-end? (defun dojo-js-indent-get-prev-newline (prev-ignore-block-end) (save-excursion (search-backward "\n" prev-ignore-block-end t)) ) (defun dojo-js-indent-get-prev-round-bracket-open (prev-ignore-block-end) (save-excursion (search-backward "(" prev-ignore-block-end t)) ) (defun dojo-js-indent-get-prev-round-bracket-close (prev-ignore-block-end) (save-excursion (search-backward ")" prev-ignore-block-end t)) ) (defun dojo-js-indent-get-prev-square-bracket-open (prev-ignore-block-end) (save-excursion (search-backward "[" prev-ignore-block-end t)) ) (defun dojo-js-indent-get-prev-square-bracket-close (prev-ignore-block-end) (save-excursion (search-backward "]" prev-ignore-block-end t)) ) (defun dojo-js-indent-get-prev-curly-bracket-open (prev-ignore-block-end) (save-excursion (search-backward "{" prev-ignore-block-end t)) ) (defun dojo-js-indent-get-prev-curly-bracket-close (prev-ignore-block-end) (save-excursion (search-backward "}" prev-ignore-block-end t)) ) (defun dojo-js-indent-greater-p (a b) (if (null a) nil ; Cases "nil > nil" and "nil > 5" both evaluate to false (if (null b) t ; Case "5 > nil" evaluates to t (> a b)))) (defun dojo-js-indent-jump-to-string-literal-start (ch) (search-backward ch nil t) ) (defun dojo-js-indent-jump-to-line-comment-start () (search-backward "//" nil t) ) (defun dojo-js-indent-jump-to-block-comment-start () (search-backward "/*" nil t) ) (defun dojo-js-indent-jump-to-string-literal-end (ch) (search-forward ch nil t) ) (defun dojo-js-indent-jump-to-line-comment-end () (move-end-of-line nil) (point) ) (defun dojo-js-indent-jump-to-block-comment-end () (search-forward "*/" nil t) ) (defun dojo-js-indent-get-initial-trigger-indent-index (indent-index indent-chars) (let* ((round-level 0) (square-level 0) (curly-level 0) (trigger-indent-index nil)) (while (and (>= indent-index 0) (null trigger-indent-index)) (let* ((indent-char (nth indent-index indent-chars)) (char (dojo-js-indent-char-char indent-char)) (line (dojo-js-indent-char-line indent-char)) (state (dojo-js-indent-char-state indent-char))) (cond ((= char ?\() (decf round-level)) ((= char ?\)) (incf round-level)) ((= char ?\[) (decf square-level)) ((= char ?\]) (incf square-level)) ((= char ?\{) (decf curly-level)) ((= char ?\}) (incf curly-level))) (cond ((< round-level 0) (setq trigger-indent-index indent-index)) ((< square-level 0) (setq trigger-indent-index indent-index)) ((< curly-level 0) (setq trigger-indent-index indent-index))) ; ; If we find something like ; ; 'function(...) {', possibly in a multi-line fashion, ; ; then we use the indent-char '(' instead of '{' as reference. ; ; The code below detects wether we are in that situation. ; (let* ((prev-indent-char (nth (1- indent-index) indent-chars)) ; (prev-char (dojo-js-indent-char-char prev-indent-char)) ; (prev-line (dojo-js-indent-char-line prev-indent-char)) ; (prev-prev-indent-index (- indent-index 2)) ; (prev-prev-indent-char (nth prev-prev-indent-index indent-chars)) ; (prev-prev-char (dojo-js-indent-char-char prev-prev-indent-char))) ; (if (and (= prev-char ?\)) (= prev-line line) (= prev-prev-char ?\()) ; (setq trigger-indent-index prev-prev-indent-index) ; (setq trigger-indent-index indent-index))))) (decf indent-index))) (log-indent (format "Calculated initial-trigger-indent-index [%s]" trigger-indent-index)) trigger-indent-index)) (defun dojo-js-indent-get-trigger-indent-index (indent-index initial-trigger-indent-index indent-chars) (let* ((initial-trigger-indent-char (nth initial-trigger-indent-index indent-chars)) (initial-trigger-line (dojo-js-indent-char-line initial-trigger-indent-char)) (initial-trigger-char (dojo-js-indent-char-char initial-trigger-indent-char))) (log-indent (format "Calculating trigger-indent-index, initial-trigger-char is [%s]" (char-to-string initial-trigger-char))) (log-indent (format "... line [%s]" line)) (cond ((= initial-trigger-char ?\{) ; If we find something like ; 'function(...) {', possibly in a multi-line fashion, ; then we use the indent-char '(' instead of '{' as reference. ; The code below detects wether we are in that situation. (let* ((prev-indent-char (nth (1- initial-trigger-indent-index) indent-chars)) (prev-char (dojo-js-indent-char-char prev-indent-char)) (prev-line (dojo-js-indent-char-line prev-indent-char)) (prev-prev-indent-index (- initial-trigger-indent-index 2)) (prev-prev-indent-char (nth prev-prev-indent-index indent-chars)) (prev-prev-char (dojo-js-indent-char-char prev-prev-indent-char))) (log-indent (format "prev-char [%s], prev-line [%s], prev-prev-char [%s]" (char-to-string prev-char) prev-line (char-to-string prev-prev-char))) (if (and (= prev-char ?\)) (= prev-line initial-trigger-line) (= prev-prev-char ?\()) prev-prev-indent-index initial-trigger-indent-index))) (t initial-trigger-indent-index)))) (defun dojo-js-indent-get-line-indentation (indent-char) (let ((line (dojo-js-indent-char-line indent-char))) (save-excursion (goto-line line) (back-to-indentation) (current-column)))) (defun dojo-js-indent-get-curr-line-indentation-point () (save-excursion (back-to-indentation) (point))) (defun dojo-js-indent-get-line-indentation-point (line) (save-excursion (goto-line line) (back-to-indentation) (point))) (defun dojo-js-indent-step-forward () "Starting at point, this function traverses the source code forward until some condition is met." (save-excursion (let* ((curly-bracket-level 0) (next-line-comment-start (dojo-js-indent-get-next-line-comment-start)) ; (prev-line-comment-end (dojo-js-indent-get-prev-line-comment-end prev-line-comment-start)) ; (prev-block-comment-end (dojo-js-indent-get-prev-block-comment-end)) (next-block-comment-start (dojo-js-indent-get-next-block-comment-start)) (next-single-quote (dojo-js-indent-get-next-single-quote)) (next-double-quote (dojo-js-indent-get-next-double-quote)) (next-ignore-block-start (dojo-js-indent-get-next-ignore-block-start next-single-quote next-double-quote next-line-comment-start next-block-comment-start)) (next-curly-bracket-open (dojo-js-indent-get-next-curly-bracket-open next-ignore-block-start)) (next-curly-bracket-close (dojo-js-indent-get-next-curly-bracket-close next-ignore-block-start)) (next-colon (dojo-js-indent-get-next-colon next-ignore-block-start)) (members ()) (iteration 0)) (while (and (not (null next-ignore-block-start)) (not (null curly-bracket-level)) (>= curly-bracket-level 0)) ; (log-indent (format "Iteration %s: Levels {=%s; next-ignore-block-start [%s]" iteration curly-bracket-level next-ignore-block-start)) ; (log-indent (format "... Ignore-blocks: //=%s, /*=%s, '=%s, \"=%s" next-line-comment-start next-block-comment-start next-single-quote next-double-quote)) ; (log-indent (format "... Characters: {=%s, }=%s, :=%s" next-curly-bracket-open next-curly-bracket-close next-colon)) (let* ((last-token (dojo-common-math-min-with-identifier (list ?\{ next-curly-bracket-open) (list ?\} next-curly-bracket-close) (list ?\: next-colon))) (last-char (if (null last-token) nil (nth 0 last-token))) (last-pos (if (null last-token) nil (nth 1 last-token))) (ignore-block-end nil)) ; Only set if we decide to jump over the next ignore block ; If there is an interesting character before point, but after prev-ignore-block-end, move point there. ; (if (not (null last-char)) (log-indent (format "... Located last-char %c at pos [%s]" last-char last-pos))) (if (not (null last-pos)) (goto-char last-pos)) (cond ((null last-char) ; If there is no further interesting character between the current position, and the start of the next ; ignore block, we need to step forward over that ignore block. (if (= next-ignore-block-start 0) (setq curly-bracket-level nil) (goto-char next-ignore-block-start) (cond ((eq next-ignore-block-start next-single-quote) (setq ignore-block-end (dojo-js-indent-jump-to-string-literal-end "'")) ; (log-indent (format "... Starting at [%s], found ignore-block-end [%s] based on '" next-ignore-block-start ignore-block-end)) ) ((eq next-ignore-block-start next-double-quote) (setq ignore-block-end (dojo-js-indent-jump-to-string-literal-end "\"")) ; (log-indent (format "... Starting at [%s], found ignore-block-end [%s] based on \"" next-ignore-block-start ignore-block-end)) ) ((eq next-ignore-block-start next-line-comment-start) (setq ignore-block-end (dojo-js-indent-jump-to-line-comment-end)) ; (log-indent (format "... Starting at [%s], found ignore-block-end [%s] based on //" next-ignore-block-start ignore-block-end)) ) ((eq next-ignore-block-start next-block-comment-start) (setq ignore-block-end (dojo-js-indent-jump-to-block-comment-end)) ; (log-indent (format "... Starting at [%s], found ignore-block-end [%s] based on /*" next-ignore-block-start ignore-block-end)) ) (t (log-indent (format "Unrecognized next-ignore-block-end when trying to step to ignore-block-end at pos [%s]" next-ignore-block-start)) (setq indentation-level 0))))) ; Set to zero indentation, to avoid triggering an endless loop if a bug really triggers this case ; If we found an interesting character, adapt the stored information (e.g. nesting level of brackets), ((= last-char ?\{) (incf curly-bracket-level) (setq next-curly-bracket-open (dojo-js-indent-get-next-curly-bracket-open next-ignore-block-start))) ((= last-char ?\}) (decf curly-bracket-level) (setq next-curly-bracket-close (dojo-js-indent-get-next-curly-bracket-close next-ignore-block-start))) ((= last-char ?\:) (if (= curly-bracket-level 0) (let* ((line-start-pos (save-excursion (move-beginning-of-line nil) (point))) (colon-pos (dojo-js-indent-get-object-member-colon line-start-pos)) (member-start (dojo-js-indent-get-object-member-start line-start-pos colon-pos)) (prefix-with-spaces (buffer-substring-no-properties line-start-pos last-pos)) (match-result (string-match "^[[:space:]]*[[:alnum:]]+[[:space:]]*:" prefix-with-spaces nil))) ; (log-indent (format "Processing colon, line-start-pos = [%s], prefix-with-spaces = [%s], match-result = [%s]" ; line-start-pos prefix-with-spaces match-result)) (if (eq match-result 0) (progn ; (log-indent (format "Case match-result = 0, pushing [%s]" ; (dojo-common-strings-trim (substring prefix-with-spaces 0 (1- (length prefix-with-spaces)))))) (let* ((width (- colon-pos member-start)) (line (line-number-at-pos)) (member (construct-dojo-js-indent-member width line))) (push member members)))))) ; Use this line to obtain the identifiers, instead of the prefix lengths ; (push (dojo-common-strings-trim (substring prefix-with-spaces 0 (1- (length prefix-with-spaces)))) result))))) (setq next-colon (save-excursion (search-forward ":" next-ignore-block-start t)))) (t (log-indent (format "Unknown character '%s' when determining what to do next; cases in source code seem to be inconsistent." last-char)) (setq curly-bracket-level nil))) ; Set to zero indentation, to avoid triggering an endless loop if a bug really triggers this case (if (and (not (null curly-bracket-level)) (>= curly-bracket-level 0)) (if (null ignore-block-end) ; If ignore-block-end is nil, we found an interesting character before the ignore block, and did not step forward over it. ; Thus, we jump to that character, and proceed forward from that point. (goto-char last-pos) ; Else, we jumped to the end of the ignore block above, and now have to update the information about other ; interesting characters and ignore blocks after point. (if (and (not (null next-line-comment-start)) (<= next-line-comment-start ignore-block-end)) (setq next-line-comment-start (dojo-js-indent-get-next-line-comment-start))) (if (and (not (null next-block-comment-start)) (<= next-block-comment-start ignore-block-end)) (setq next-block-comment-start (dojo-js-indent-get-next-block-comment-start))) (if (and (not (null next-single-quote)) (<= next-single-quote ignore-block-end)) (setq next-single-quote (dojo-js-indent-get-next-single-quote))) (if (and (not (null next-double-quote)) (<= next-double-quote ignore-block-end)) (setq next-double-quote (dojo-js-indent-get-next-double-quote))) ; Update the next-ignore-block-start (setq next-ignore-block-start (dojo-js-indent-get-next-ignore-block-start next-single-quote next-double-quote next-line-comment-start next-block-comment-start)) (setq next-curly-bracket-open (dojo-js-indent-get-next-curly-bracket-open next-ignore-block-start)) (setq next-curly-bracket-close (dojo-js-indent-get-next-curly-bracket-close next-ignore-block-start)) (setq next-colon (dojo-js-indent-get-next-colon next-ignore-block-start)))) (incf iteration))) members))) (defun dojo-js-indent-get-next-line-comment-start () (save-excursion (search-forward "//" nil t)) ) (defun dojo-js-indent-get-next-block-comment-start () (save-excursion (search-forward "/*" nil t)) ) (defun dojo-js-indent-get-next-single-quote () (save-excursion (search-forward "'" nil t)) ) (defun dojo-js-indent-get-next-double-quote () (save-excursion (search-forward "\"" nil t)) ) (defun dojo-js-indent-get-next-ignore-block-start (next-single-quote next-double-quote next-line-comment-start next-block-comment-start) (dojo-common-math-nil-aware-min next-single-quote next-double-quote next-line-comment-start next-block-comment-start) ) (defun dojo-js-indent-get-next-curly-bracket-open (next-ignore-block-start) (save-excursion (search-forward "{" next-ignore-block-start t)) ) (defun dojo-js-indent-get-next-curly-bracket-close (next-ignore-block-start) (save-excursion (search-forward "}" next-ignore-block-start t)) ) (defun dojo-js-indent-get-next-colon (next-ignore-block-start) (save-excursion (search-forward ":" next-ignore-block-start t)) ) (defun dojo-js-indent-looks-like-object-member-line (point-to-indent) "Returns wether the line at the given position looks like an object member specification. Does this job in an heuristic manner, i.e. there might be cases where e.g. a sequence of identifier and colon is misinterpreted as object member specification." ; Object member lines start with some identifier, followed by a colon. (and (save-excursion (goto-char point-to-indent) (let* ((line-string (thing-at-point 'line t)) (match-pos (string-match "^[[:space:]]*[[:alnum:]]+[[:space:]]*:" line-string nil))) (eq match-pos 0) )))) ; Don't apply the "indent centered around ':'"-logic for the member functions / variables below the declare line. (defun dojo-js-indent-do-object-indentation (reference-level trigger-indent-char) (let* ((trigger-line (dojo-js-indent-char-line trigger-indent-char)) (trigger-line-string (dojo-common-strings-get-trimmed-line trigger-line))) (not (and (= reference-level dojo-indent-width) (not (null (string-match "\\(=\\|return\\)[[:space:]]*\\(declare(\\|\{\\)" trigger-line-string nil))))))) (defun dojo-js-indent-get-object-member-colon (point-to-indent) (save-excursion (goto-char point-to-indent) (move-beginning-of-line nil) (1- (search-forward ":" (save-excursion (move-end-of-line nil) (point)) t)))) (defun dojo-js-indent-get-object-member-start (point-to-indent limit) (save-excursion (goto-char point-to-indent) (move-beginning-of-line nil) (1- (re-search-forward "[[:alnum:]]" limit t)))) (provide 'dojo-js-indent)