diff --git a/fcore/app-update.sml b/fcore/app-update.sml index a258231..0937594 100644 --- a/fcore/app-update.sml +++ b/fcore/app-update.sml @@ -116,6 +116,22 @@ struct in (newApp, drawMsg) end + + fun nextWord (app: app_type) = + let + val {buffer, windowWidth, windowHeight, startLine, cursorIdx, ...} = app + + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val cursorIdx = Cursor.nextWord (buffer, cursorIdx) + + val buffer = LineGap.goToLine (startLine, buffer) + val drawMsg = TextBuilder.build + (startLine, cursorIdx, buffer, windowWidth, windowHeight) + + val newApp = AppWith.bufferAndCursorIdx (app, buffer, cursorIdx) + in + (newApp, drawMsg) + end fun handleChr (app: app_type, chr) = case chr of @@ -125,6 +141,7 @@ struct | #"l" => moveRight app | #"0" => moveToLineStart app | #"$" => moveToLineEnd app + | #"w" => nextWord app | _ => (app, []) fun update (app, msg) = diff --git a/fcore/cursor.sml b/fcore/cursor.sml index 201a9f2..4887ad9 100644 --- a/fcore/cursor.sml +++ b/fcore/cursor.sml @@ -838,4 +838,142 @@ struct (* nowhere to go rightward, so return cursorIdx *) cursorIdx end + + (* + * nvim's motion.txt document describes a word as: + * - A sequence of (letters, digits and underscores) + * - or a sequence of other non-blank characters + * - separated by white space (space, tab, ) + *) + datatype word_type = + ALPHA_NUM + | SPACE + | NON_BLANK + + fun getWordType chr = + if Char.isAlphaNum chr orelse chr = #"_" then + ALPHA_NUM + else if Char.isSpace chr then + SPACE + else + NON_BLANK + + fun helpNextWordString + ( strPos, str, absIdx + , strTl, lineTl + , wordType, hasPassedSpace) = + if strPos = String.size str then + helpNextWordList + (strTl, lineTl, absIdx, wordType, hasPassedSpace) + else + let + val chr = String.sub (str, strPos) + in + if Char.isAlphaNum chr orelse chr = #"_" then + case wordType of + ALPHA_NUM => + (* current chr is ALPHA_NUM + * and previous chr was also ALPHA_NUM + * so continue iterating *) + helpNextWordString + ( strPos + 1, str, absIdx + 1 + , strTl, lineTl + , ALPHA_NUM, hasPassedSpace + ) + | SPACE=> + (* we moved from SPACE to ALPHA_NUM, + * meaning we may have reached a new word. *) + if hasPassedSpace then + absIdx + else + helpNextWordString + ( strPos + 1, str, absIdx + 1 + , strTl, lineTl + , ALPHA_NUM, true + ) + | NON_BLANK=> + (* moved from NON_BLANK to ALPHA_NUM, + * meaning we reached new word *) + absIdx + else if Char.isSpace chr then + (* nothing to do on space, except keep iterating *) + helpNextWordString + ( strPos + 1, str, absIdx + 1 + , strTl, lineTl + , SPACE, true + ) + else + (* chr is NON_BLANK. *) + case wordType of + NON_BLANK => + (* moved from non-blank to non-blank + * so keep iterating *) + helpNextWordString + ( strPos + 1, str, absIdx + 1 + , strTl, lineTl + , NON_BLANK, hasPassedSpace + ) + | ALPHA_NUM => + (* moved from ALPHA_NUM to non-blank + * so we have reached new word *) + absIdx + | SPACE => + (* from space to non-blank *) + if hasPassedSpace then + absIdx + else + helpNextWordString + ( strPos + 1, str, absIdx + 1 + , strTl, lineTl + , NON_BLANK, true + ) + end + + and helpNextWordList (strings, lines, absIdx, wordType, hasPassedSpace) = + case (strings, lines) of + (strHd::strTl, lineHd::lineTl) => + helpNextWordString + ( 0, strHd, absIdx, strTl, lineTl, wordType, hasPassedSpace ) + | (_, _) => + (* reached end of lineGap; + * return last valid chr position *) + absIdx - 1 + + fun startNextWord (shd, strIdx, absIdx, stl, ltl) = + let + val chr = String.sub (shd, strIdx) + val wordType = getWordType chr + val isSpace = wordType = SPACE + in + helpNextWordString + (strIdx + 1, shd, absIdx + 1, stl, ltl, wordType, isSpace) + end + + fun nextWord (lineGap: LineGap.t, cursorIdx) = + let + val {rightStrings, rightLines, idx = bufferIdx, ...} = lineGap + in + case (rightStrings, rightLines) of + (shd :: stl, lhd :: ltl) => + let + (* convert absolute cursorIdx to idx relative to hd string *) + val strIdx = cursorIdx - bufferIdx + in + if strIdx < String.size shd then + (* strIdx is in this string *) + startNextWord (shd, strIdx, cursorIdx, stl, ltl) + else + (* strIdx is in tl *) + (case (stl, ltl) of + (stlhd :: stltl, ltlhd :: ltltl) => + let + val strIdx = strIdx - String.size shd + in + startNextWord + (stlhd, strIdx, cursorIdx, stltl, ltltl) + end + | (_, _) => cursorIdx) + end + | (_, _) => cursorIdx + end end diff --git a/shf b/shf index 7f3b0b8..0739edf 100755 Binary files a/shf and b/shf differ