我想用Haskell,hscurses和Data.Text來構建一個簡單的文本編輯器。我對Haskell很新。Haskell「字符串移動」函數
這裏是我的代碼片段:
data Cursor = Cursor {
position :: Int,
line :: Int,
column :: Int
} deriving (Eq, Show)
isNewline :: Char -> Bool
isNewline c = c == '\n'
onNewline :: T.Text -> Int -> Bool
onNewline buf pos
| pos >= T.length buf = False
| otherwise = isNewline $ T.index buf pos
findIndex :: (Char -> Bool) -> T.Text -> Int -> Maybe Int
findIndex pred buf pos
| buf == T.empty = Just 0
| otherwise = rightWhile pos
where rightWhile pos
| pos > bufMax buf = Nothing
| pred $ T.index buf pos = Just pos
| otherwise = rightWhile (pos + 1)
findIndexLeft :: (Char -> Bool) -> T.Text -> Int -> Maybe Int
findIndexLeft pred buf pos = leftWhile pos
where leftWhile pos
| pos < 0 = Nothing
| pred $ T.index buf pos = Just pos
| otherwise = leftWhile (pos - 1)
startOfLine :: T.Text -> Int -> Int
startOfLine buf pos = case findIndexLeft isNewline buf (pos - 1) of
Nothing -> 0
Just p -> p + 1
endOfLine :: T.Text -> Int -> Int
endOfLine buf pos = case findIndex isNewline buf pos of
Nothing -> 1 + bufMax buf
Just p -> p
lineOffset :: T.Text -> Int -> Int
lineOffset buf pos = pos - startOfLine buf pos
lineLength :: T.Text -> Int -> Int
lineLength buf pos = endOfLine buf pos - startOfLine buf pos
bufMax :: T.Text -> Int
bufMax buf = max 0 $ T.length buf - 1
bufLines :: T.Text -> Int
bufLines = T.foldl (\acc c -> if isNewline c then (acc+1) else acc) 0
moveCursorRight :: T.Text -> Cursor -> Cursor
moveCursorRight buf [email protected](Cursor pos line col)
| buf == T.empty = c
| otherwise = Cursor newPos newLine newCol
where end = 1 + bufMax buf
onEnd = pos == end
newPos = clip (pos + 1) 0 end
newLine = if onNewline buf pos && not onEnd
then line + 1
else line
newCol = lineOffset buf newPos
moveCursorLeft :: T.Text -> Cursor -> Cursor
moveCursorLeft buf (Cursor pos line col) =
Cursor newPos newLine newCol
where onStart = pos == 0
newPos = clipLow (pos - 1) 0
newLine = if onNewline buf newPos && not onStart
then line - 1
else line
newCol = lineOffset buf newPos
-- More movement functions follow...
這段代碼的問題是,對於那些成千上萬行的長的緩衝區,它會非常緩慢。這可能是因爲使用索引函數,例如O(n)而不是常量時間,例如它們將在C中。
一個經驗豐富的哈斯克勒會如何處理這個問題?在Haskell中的字符串中實現「移動」是一種合理有效的方式?運動也應該是組合的,那就是,我想能夠在「向下移動一行」條款實施「向下翻頁」等
編輯:更新
如若有誰需要這個,這是我結束了。
type Line = T.Text
data BufferContext = BufferContext {
before :: [Line],
at :: Line,
after :: [Line]
} deriving (Eq, Show)
moveCursorRight :: Cursor -> Cursor
moveCursorRight [email protected](Cursor pos line col [email protected](BufferContext before at after))
| col >= T.length at = moveCursorDown c
| otherwise = Cursor (pos+1) line (col+1) bc
moveCursorLeft :: Cursor -> Cursor
moveCursorLeft [email protected](Cursor pos line col [email protected](BufferContext before at after))
| col <= 0 = upCursor { column = if null before then 0 else T.length $ head before }
| otherwise = Cursor (pos-1) line (col-1) bc
where upCursor = moveCursorUp c
moveCursorDown :: Cursor -> Cursor
moveCursorDown [email protected](Cursor _ _ _ (BufferContext _ _ [])) = c
moveCursorDown [email protected](Cursor _ cLine _ (BufferContext before at (l:ls))) =
c { line = cLine+1,
column = 0,
context = BufferContext (at:before) l ls
}
moveCursorUp [email protected](Cursor _ _ _ (BufferContext [] _ _)) = c
moveCursorUp [email protected](Cursor _ cLine _ (BufferContext (l:ls) at after)) =
c { line = cLine-1,
column = 0,
context = BufferContext ls l (at:after)
}
該實現在100萬行上非常實用,對我來說這已經夠用了。但是,這種方法仍然存在一個問題。如果我想跳到一條隨機線,我必須逐個移動,這可能會很慢。但是,與原始方法相比,這是一個很大的改進。
我也曾嘗試執行上下文
data BufferContext = BufferContext {
before :: T.Text,
at :: Char,
after :: T.Text
} deriving (Eq, Show)
但這並不能幫助太多,因爲「在」必須「之前」與consed,並根據該文件,T.cons
是O( n)...而且,當實際顯示完成時,以線爲中心的方法更好。
感謝所有幫助過的人!
您可能想要使用[Zipper](https://en.wikipedia.org/wiki/Zipper_\(data_structure \))來表示您的當前位置,將緩衝區表示爲行列表。對於Agda中的指導示例(您可以在Haskell中複製相當多的工作),您可以查看[本練習](https://github.com/pigworker/CS410-14/blob/master/Ex5 /編輯。Agda) – gallais
另請參見:https://stackoverflow.com/questions/4046246/how-are-text-editors-generally-implemented。有一種方法可以將'findIndex'加速到O(n),但之後仍然使用索引位置,所以從長遠來看它並不能真正幫助你。 – Zeta
如果你需要跳過,你應該使用'Data.Sequence'從* list * zipper切換到* sequence * zipper。 '數據拉鍊a =拉鍊(seq a)a(seq a)'或類似的。與列表拉鍊不同,您可以保持一切順序,因爲結束的速度與開始時一樣快。你可以在'O(log n)'時間內移動'n'個步驟。 – dfeuer