# ============================================================================== # This file contains the following functions: # # PlainText_setSettings # PlainText_startOfLine # PlainText_getLine # PlainText_getFirstCol # PlainText_getBreakPos # PlainText_NewlineMacro # PlainText_TypeInMacro # # PlainText_getLineEnd # PlainText_getLineBegin # PlainText_LineNonEmpty # PlainText_ParagraphBegin # PlainText_ParagraphEnd # PlainText_GetLeftMargins # PlainText_ParagraphMargins # PlainText_IndentString # PlainText_1ParagraphFill # PlainText_ParagraphFill # # ============================================================================== # These macros are designed to be used with a "Plain text" language. # Thanks to Oliver Schmidt for the idea! # # The principle behind the "Plain text" language is that all "soft" new lines # (ie new lines which can be removed on reformatting a paragraph) occur after a # space or tab character. Any other new line character is considered "hard", # marking the end of a paragraph. The only exception is a line containing only # spaces and tabs: this is considered equivalent to an empty line (one holding # only the new line character). # # For the "Plain text" language to work, we use a macro-controlled line # breaking mechanism for typing; if typing passes beyond the $wrap_margin # column, only spaces can be added to the line; anything else will cause a # new line character (and indenting) to be inserted. If you just carry on # typing, this means that word-wrapping of typed text will occur, leaving # trailing spaces at the ends of earlier lines. This means that, unless you add # a new line explicitly, everything you type will be considered as one # paragraph for reformatting. # # For reformatting a paragraph, you need to adjust the value of $wrap_margin. # This can be done via Peferences->Wrap->Wrap Margin... # # If you want the first line of a paragraph to be treated specially (to preserve # first line indentations), you should set the value of the global variable # $PlainText_handleFirstLineOfPara (declared below) to 1. (This is global to # all windows.) # # Set up the language (Preferences->Default Settings->Language Modes...) # as follows: # Name: Plain text # Smart-indent # No wrapping # You shouldn't need to add anything else, though you might want to. # # Don't bother with syntax highlighting patterns: this is supposed to be plain # text, so your default background and foreground should be OK. # # You will also need to set up the Smart-indent macros # (Preferences->Default Settings->Auto Indent->Program Smart Indent...) # as follows: # Language Mode: Plain text # Language Specific Initialization Macro Commands and Definitions: # # Load up the "Plain text" macro file # NEDIT_require_macro_file("PlainText.nm") # # (you could perhaps use something like # # load_macro_file($my_macros_directory"/PlainText.nm") # # if you don't use the NEDIT_LOADED.nm macros) # # # # You may want to handle the first line of a paragraph as an # # exception - if so, uncomment the following line # # $PlainText_handleFirstLineOfPara = 1 # # Newline Macro: # return PlainText_NewlineMacro($1) # # Type-in Macro # PlainText_TypeInMacro($1, $2) # # None of the "Common / Shared Initialization" routines are used, although I'm # sure they could be used in place of some of the following. # # Now add macros (Preferences->Default Settings->Cusomize Menus->Macro Menu...) # to reformat (refill) a paragraph and so on: # # Menu Entry: Refill Paragraphs@Plain text # Accelerator: Alt+J # Requires Selection: off # Macro Command to Execute: # NEDIT_require_macro_file("PlainText.nm") # # if ($selection_left != -1) # return dialog("Cannot refill a paragraph in a " \ # "rectangular selection") # # start = $cursor # end = $cursor # if ($selection_start != -1) # { # set_cursor_pos($selection_start) # start = $selection_start # end = $selection_end # } # else # { # start = PlainText_ParagraphBegin(get_range(0, $text_length), \ # $cursor) # end = PlainText_ParagraphEnd(get_range(0, $text_length), \ # $cursor) # set_cursor_pos(start) # } # # p = PlainText_ParagraphFill(get_range(start, end), $column, \ # 0, end - start) # replace_range(start, end, p) # set_cursor_pos(start + length(p)) # # Menu Entry: Toggle 1st Line Handling@Plain text # Accelerator: # Requires Selection: off # Macro Command to Execute: # NEDIT_require_macro_file("PlainText.nm") # # $PlainText_handleFirstLineOfPara = \ # !$PlainText_handleFirstLineOfPara # # c = "off\n\n" \ # "Margin of first line to be used\n" \ # "for whole paragraph" # if ($PlainText_handleFirstLineOfPara) # c = "on\n\n" \ # "Margin of first line to be handled separately\n" \ # "will use second line's margin" # dialog("First line handling = " c) # # Menu Entry: Select Paragraph@Plain text # Accelerator: # Requires Selection: off # Macro Command to Execute: # NEDIT_require_macro_file("PlainText.nm") # # paraBeg = PlainText_ParagraphBegin(get_range(0, $text_length), \ # $cursor) # paraEnd = PlainText_ParagraphEnd(get_range(0, $text_length), \ # $cursor) # select(paraBeg, paraEnd) # set_cursor_pos(paraEnd) # # Once you've done that, open a window, use "Preferences->Language Mode" to set # the current language to "Plain text", and start typing. # ============================================================================== $PlainText_lastPos = -1 $PlainText_firstColPos = 0 $PlainText_lineEnd = 0 $PlainText_lineEndChar = "" $PlainText_handleFirstLineOfPara = 0 $PlainText_handleCommonPrefix = 0 $PlainText_lastNewlinePrefix = "" #$DEBUG_PlainText_POS = 0 #NEDIT_require_macro_file("DEBUG.nm") #DEBUG("Plain Text debugging\n\n") #$DEBUG_PlainText_POS = DEBUG_mark_to() # ============================================================================== # PlainText_setSettings(): toggles the PlainText configuration switches. # ============================================================================== define PlainText_setSettings { items[0] = "First line handling" items[1] = "Common Prefix handling" for (;;) { s = "" if ($PlainText_handleFirstLineOfPara) c = "on\n\n" \ "Margin of first line to be handled separately\n" \ "will use second line's margin" else c = "off\n\n" \ "Margin of first line to be used for whole\n" \ "paragraph" s = s items[0] " = " c "\n\n" if ($PlainText_handleCommonPrefix) { c = "on\n\n" \ "The new line will be space indented to\n" \ "the same degree as the current line" if ($PlainText_handleFirstLineOfPara) c = c "\n" \ "(along with any extra spaces from the\n" \ "current line)" } else c = "off\n\n" \ "The new line will use the common prefix\n" \ "between the current line and the next" s = s items[1] " = " c "\n\n" s = s "\n\n>>> Toggle which setting?" list = "" for (i = 0; i in items; i++) { list = list items[i] "\n" } s = list_dialog(s, substring(list, 0, -1), "OK", "Cancel") if (s != "" && $list_dialog_button == 1) { for (i = 0; i in items; i++) if (items[i] == s) break if (i == 0) $PlainText_handleFirstLineOfPara = ! $PlainText_handleFirstLineOfPara else if (i == 1) $PlainText_handleCommonPrefix = ! $PlainText_handleCommonPrefix } else break } } # ============================================================================== # PlainText_startOfLine(position): Returns the position of the first character # on the line containing the position passed. # ============================================================================== define PlainText_startOfLine { # find end of THIS line e = PlainText_endOfLine($1) # now find the previous one pe = search("\n", e - 1, "case", "backward") if (pe < 0) return 0 return $search_end # for (i = $1 - 1; ; i--) # { # if (i <= 0) # return 0 # if (get_character(i) == "\n") # return i + 1 # } } # ============================================================================== # PlainText_endOfLine(position): Returns the position of the newline character # at the end of the line containing the position passed, or the end of the # file. # ============================================================================== define PlainText_endOfLine { e = search("\n", $1, "case") if (e == -1) return $text_length return e # for (i = $1; ; i++) # { # if (i == $text_length) # return $text_length # if (get_character(i) == "\n") # return i # } } # ============================================================================== # PlainText_getLine(position): Returns a copy of the current line upto the # position. # ============================================================================== define PlainText_getLine { return get_range(PlainText_startOfLine($1), $1) } # ============================================================================== # PlainText_getWholeLine(position): Returns a copy of the current line upto the # end of the line. # ============================================================================== define PlainText_getWholeLine { return get_range(PlainText_startOfLine($1), PlainText_endOfLine($1)) } # ============================================================================== # PlainText_getCommonPrefix(position): finds the common prefix between the # current and previous lines, and returns it. The position in the string # of the first character not of the prefix (ie the prefix length) is # returned in $PlainText_firstColPos # ============================================================================== define PlainText_getCommonPrefix { pos = $1 curr = PlainText_startOfLine(pos) i = 0 if (curr == 0) { # current line is first in file for (i = 0; 1; i++) { c = get_character(curr + i) if (c != " " && c != "\t") break } } else { prev = PlainText_startOfLine(curr - 1) #DEBUG("PlainText_getCommonPrefix:\nprev start = "prev"\ncurr start = "curr"\n") c = "" p = "" i = -1 while (c == p && c != "\n") { i++ c = get_character(curr + i) p = get_character(prev + i) } # if the previous line is *only* prefix or $PlainText_handleFirstLineOfPara # is true, any extra spaces on the current line are treated as valid for # the prefix. if (p == "\n" || $PlainText_handleFirstLineOfPara) while (c == " " || c == "\t") { i++ c = get_character(curr + i) } } s = get_range(curr, curr + i) $PlainText_firstColPos = i #DEBUG("prefix length = "i"\nprefix = "s"\n") return s } # ============================================================================== # PlainText_getFirstCol(line, col, pos): Returns the column of the first # non-space character in line. The position in the string of the character # is given in $PlainText_firstColPos. # ============================================================================== define PlainText_getFirstCol { col = $2 p = 0 if ($n_args >= 3) p = $3 while (1) { c = substring($1, p, p + 1) if (c == " ") col += 1 else if (c == "\t") col = col + $tab_dist - (col % $tab_dist) else break p++ } $PlainText_firstColPos = p return col } # ============================================================================== # PlainText_getBreakPos(line, m_right, col): Returns the position of the # character following the space/tab sequence which comes before or # runs over the column specified by m_right (default $wrap_margin). # Column counting starts from col (default 0), which must be less than # m_right. If no break is needed, returns zero. # ============================================================================== define PlainText_getBreakPos { m_right = $wrap_margin if ($n_args >= 2 && $2 > 0) m_right = $2 col = 0 if ($n_args >= 3 && $3 >= 0 && $3 < m_right) col = $3 brk = 0 c = "" for (p = 0; col < m_right; p++) { c = substring($1, p, p + 1) if (c == "") { return 0 # we are at the end: no break needed } else { if (c == " ") { col += 1 brk = p } else if (c == "\t") { col = col + $tab_dist - (col % $tab_dist) brk = p } else col++ } } for (c = substring($1, brk, brk + 1); \ c == "\t" || c == " "; \ c = substring($1, brk, brk + 1)) brk++ return brk } # ============================================================================== # PlainText_NewlineMacro(position): Macro called for the plain text language's # automatic newlining. Returns 0 or -1. # Note: Newline macros will be called by NEdit if automatic word wrap is used. # If this is the case, the Newline macro commands get called after the type-in # commands, with the same position ($1). If the action routine newline() was # called, however, the position in the two commands will be different. # ============================================================================== define PlainText_NewlineMacro { l = PlainText_getLine($1) if (PlainText_getFirstCol(l, 0) >= $wrap_margin) { return 0 } if ($PlainText_handleFirstLineOfPara) { if (!$PlainText_handleCommonPrefix) { p = PlainText_startOfLine($1) if (p > 1) { p -= 1 l = PlainText_getLine(p) PlainText_getFirstCol(l, 0, 0) if ($PlainText_firstColPos == length(l)) return 0 else { len = length(l) ch = substring(l, len - 1, len) if (ch != " " && ch != "\t") return 0 } } } else # $PlainText_handleCommonPrefix { if ($1 == PlainText_startOfLine($1) && $PlainText_lastNewlinePrefix != "") insert_string($PlainText_lastNewlinePrefix) $PlainText_lastNewlinePrefix = PlainText_getCommonPrefix($1) return 0 } } return -1 } # ============================================================================== # PlainText_prefixFill(position, prefix, margin): starting at the beginning of # the line containing position, the following line is checked to see if it # has the given prefix and some following text. If it does, it is joined # to the current line, then the current line is broken if too long. # ============================================================================== define PlainText_prefixFill { position = $1 prefix = $2 margin = $wrap_margin if ($n_args > 2) margin = $3 prefixlen = length(prefix) while (1) { s = PlainText_startOfLine(position) e = PlainText_endOfLine(position) l = get_range(s, e) # find the first useful place for breaking lines brk = PlainText_getBreakPos(l, margin) if (brk <= prefixlen) { # does the next line start with the prefix? if (get_range(e + 1, e + 1 + length(prefix)) == prefix) { # yes: so join it to the current line replace_range(e, e + 1 + length(prefix), " ") continue } else { # no: stop break } } else { # we have a break position: break the line endpref = s + prefixlen brkpos = s + brk # set s to brkpos, then move back over trailing spaces s = brkpos while (s > endpref && search_string(" \t", get_character(s - 1), 0) >= 0) s-- # silly check? if (s == endpref) break # add new break between s and brkpos replace_range(s, brkpos, "\n" prefix) # and start again... position = s + prefixlen + 1 } } } # ============================================================================== # PlainText_TypeInMacro(position, charactersTyped): Macro called (through the # insert_string() action) for the plain text language's text entry. It # implicitly calls the newline macro by calling the newline() action when # the typing cursor on a line reaches the wrap margin. # Since multiple characters can be entered using insert_string(), and # here we can only guarantee useful handling of one character at a time, # we quit if the string length is not 1. # ============================================================================== define PlainText_TypeInMacro { #DEBUG_undo_to($DEBUG_PlainText_POS) #DEBUG("\n$1: insert position is: " $1) #DEBUG("\n$2: character typed is: " $2) #DEBUG("\ncharacter at insert position is: "get_character($1)"\n") if (length($2) != 1) return # don't handle this! if ($1 == PlainText_startOfLine($1) && $PlainText_lastNewlinePrefix != "") { insert_string($PlainText_lastNewlinePrefix) $PlainText_lastNewlinePrefix = "" return } $PlainText_lastPos = $1 s = PlainText_startOfLine($1) e = PlainText_endOfLine($1) if ($1 == e && ($2 == " " || $2 == "\t")) return # just add to end of line l = get_range(s, e) # find the first useful place for breaking lines brk = PlainText_getBreakPos(l) #DEBUG("\nFull line is ["s" - "e"]\n"l) #DEBUG("\nBreak to\n"get_range(s, s + brk)) #DEBUG("\nFull line breaks at "brk":\n") #DEBUG(substring(l, 0, brk)"><"substring(l, brk)"\n") if (brk <= 0) return # no need to break if (s + brk <= $1) { mark("0") if ($PlainText_handleCommonPrefix) { # use same prefix as this and the previous line str = PlainText_getCommonPrefix(s) #DEBUG("\nCursor beyond break - Prefix="str) p = s + length(str) s += brk t = s while (s > p && search_string(" \t", get_character(s - 1), 0) >= 0) --s replace_range(s, t, "\n" str) } else { # just use spaces set_cursor_pos(s + brk) # break here newline() # calls the newline macro } goto_mark("0") # this is where we want to be now } else # break position is beyond the cursor { mark("0") if ($PlainText_handleCommonPrefix) { # use same prefix as this and the previous line str = PlainText_getCommonPrefix(s) #DEBUG("\nCursor before break - Prefix="str) # p = s + length(str) # s += brk # t = s # while (s > p && search_string(" \t", get_character(s - 1), 0) >= 0) # --s # replace_range(s, t, "\n" str) PlainText_prefixFill($1, str) } goto_mark("0") # this is where we want to be now } } # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # Now for paragraph filling # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # What we want is: # 1 first line exceptioning (ie left margin exception) # 2 empty line emptying # 3 correction for non-col-zero starts # 4 no newline add if at end-of-string # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # ============================================================================== # PlainText_getLineEnd(text, pos): scans text starting from pos until a newline # or the end of text is found. Returns the position after the newline. # ============================================================================== define PlainText_getLineEnd { p = 0 if ($n_args >= 2) p = $2 if (p <= 0) p = 0 c = substring($1, p, p + 1) $PlainText_lineEndChar = c p += length(c) while (c != "" && c != "\n") { $PlainText_lineEndChar = c c = substring($1, p, p + 1) p += length(c) } $PlainText_lineEnd = p return $PlainText_lineEnd } # ============================================================================== # PlainText_getLineBegin(text, pos): scans text backwards starting from pos # until a newline or the start of text is found. Returns the position # after the newline, or zero. # ============================================================================== define PlainText_getLineBegin { p = 0 if ($n_args >= 2) p = $2 if (p <= 0) return 0 while (p > 0 && substring($1, p - 1, p) != "\n") p-- return p } # ============================================================================== # PlainText_LineNonEmpty(text, pos): returns 0 if the (first) line passed is # empty or contains only spaces. Otherwise, it returns the position after # the newline character. Searching starts at pos, if passed. # ============================================================================== define PlainText_LineNonEmpty { p = 0 if ($n_args >= 2) p = $2 if (p <= 0) p = 0 c = substring($1, p, p + 1) $PlainText_lineEndChar = c p += length(c) while (c != "" && (c == " " || c == "\t")) { $PlainText_lineEndChar = c c = substring($1, p, p + 1) p += length(c) } if (c == "" || c == "\n") { $PlainText_lineEnd = p return 0 } return PlainText_getLineEnd($1, p) } # ============================================================================== # PlainText_ParagraphBegin(text, beg): get paragraph end position from input # text, starting the analysis at beg. # ============================================================================== define PlainText_ParagraphBegin { text = $1 beg = $2 old_p = PlainText_getLineBegin(text, beg) while (old_p > 0) { p = PlainText_getLineBegin(text, old_p - 1) if (PlainText_LineNonEmpty(text, p)) { c = $PlainText_lineEndChar if (c == " " || c == "\t") { old_p = p continue } } break } return old_p } # ============================================================================== # PlainText_ParagraphEnd(text, beg): get paragraph end position from input text, # starting the analysis at beg. # ============================================================================== define PlainText_ParagraphEnd { text = $1 beg = $2 old_p = beg p = PlainText_LineNonEmpty(text, beg) c = $PlainText_lineEndChar if (c == " " || c == "\t") while (p != 0) { old_p = p p = PlainText_LineNonEmpty(text, p) c = $PlainText_lineEndChar if (p > 0 && c != " " && c != "\t" && c != "\n") { old_p = p break } } if (old_p == beg) return $PlainText_lineEnd return old_p } # ============================================================================== # PlainText_GetLeftMargins(text, col, start, end): returns a triplet, separated # by spaces. # # If the paragraph is empty (an empty line): # -1 start end_of_line_pos # If the paragraph has one line: # left_margin -1 end_of_line_pos # If the paragraph has two or more lines: # left_margin 2nd_left_margin end_of_line_pos # ============================================================================== define PlainText_GetLeftMargins { # text = $1 col = $2 start = $3 end = $4 l2 = -1 l1 = PlainText_getFirstCol($1, col, start) p = PlainText_LineNonEmpty($1, $PlainText_firstColPos) if (p == 0) return "-1 " start " " $PlainText_lineEnd if (p < end) l2 = PlainText_getFirstCol(substring($1, p), 0) return l1 " " l2 " " $PlainText_lineEnd } # ============================================================================== # PlainText_ParagraphMargins(text, col, start): returns left margins for the # first paragraph in the text - one for the first line, and one for the # second. If no second line is present, and the next (non-empty) paragraph # has the same first line margin, tries to find the second line margin of # that paragraph etc. until either all paragraphs are exhaused, or the # next paragraph starts at a different position. # ============================================================================== define PlainText_ParagraphMargins { # text = $1 col = $2 start = $3 m1 = -1 m2 = -1 m1ok = -1 end = length($1) while (m1 < 0 || m2 < 0) { end = PlainText_ParagraphEnd($1, start) if (end == start) break mm = PlainText_GetLeftMargins($1, col, start, end) col = 0 m1 = replace_in_string(mm, "([^ ]+) .*", "\\1", "regex") if (m1 >= 0) { if (m1ok < 0) m1ok = m1 else if (m1ok != m1) break # next para hasn't got same indent if (!$PlainText_handleFirstLineOfPara) { m2 = m1 break # all done - first line not different } else { m2 = replace_in_string(mm, "[^ ]+ ([^ ]+) .*", "\\1", "regex") m2end = replace_in_string(mm, "[^ ]+ [^ ]+ ([^ ]+)", "\\1", "regex") if (m2end < end && m2 >= 0) break else m2 = -1 } } start = end } if (m1ok < 0) m1ok = 0 if (m2 < 0) m2 = 0 return m1ok " " m2 } # ============================================================================== # PlainText_IndentString(indent, col): returns a string suitable for use as a # left indent. # ============================================================================== define PlainText_IndentString { n = $1 col = $2 if (n <= col) return "" if ($use_tabs) { c = col / $tab_dist c *= $tab_dist col -= c n -= c if (n >= $tab_dist) col = 0 } n -= col res = "" if ($use_tabs) while (n >= $tab_dist) { res = res "\t" n -= $tab_dist } while (n > 0) { res = res " " n-- } return res } # ============================================================================== # PlainText_1ParagraphFill(text, col, start, end, m1, m2): returns the text # from start to end filled out according to left margins m1 and m2, and # right margin $wrap_margin. start and end are assumed to be properly # set for the paragraph. col gives the initial column position. # ============================================================================== define PlainText_1ParagraphFill { # text = $1 col = $2 start = $3 end = $4 m1 = $5 m2 = $6 res = "" line = substring($1, start, end) ch = "" if (substring($1, end - 1, end) == "\n") ch = "\n" line = replace_in_string(line, "^[ \\t]*|\\n[ \\t]*", "", "regex") if (!PlainText_LineNonEmpty(line)) return "" ch # empty text left_margin = m1 indented = PlainText_IndentString(m1, col) len = length(line) while (len > 0) { brk = PlainText_getBreakPos(line, $wrap_margin, left_margin) if (!brk) return res indented line ch res = res indented substring(line, 0, brk) "\n" line = substring(line, brk) if (m1 >= 0) { left_margin = m2 col = 0 m1 = -1 indented = PlainText_IndentString(m2, col) } } return res } # ============================================================================== # PlainText_ParagraphFill(text, col, start, end, m1, m2): returns the text # from start to end filled out according to left margins m1 and m2, and # right margin $wrap_margin. start and end are assumed to be properly # set for the paragraph. col gives the initial column position. # ============================================================================== define PlainText_ParagraphFill { # text = $1 col = $2 start = $3 end = $4 res = "" while (start < end) { pos = PlainText_ParagraphEnd($1, start) if (pos > end) pos = end mm = PlainText_ParagraphMargins($1, col, start) m1 = replace_in_string(mm, "([^ ]+) .*", "\\1", "regex") m2 = replace_in_string(mm, "[^ ]+ ([^ ]+)", "\\1", "regex") para = PlainText_1ParagraphFill($1, col, start, pos, m1, m2) col = 0 res = res para start = pos } return res }