我想用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