-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy patheditbuffer.hs
More file actions
181 lines (141 loc) · 6.45 KB
/
editbuffer.hs
File metadata and controls
181 lines (141 loc) · 6.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
module EditBuffer
( EditBuffer(..)
, Location
, emptyBuffer
, enterCommandMode
, getBufferContents
, lineCount
, insertChar, deleteChar, replaceChar
, insertLineAfter
, deleteLine
, moveLeft, moveRight, moveUp, moveDown
, moveToHome, moveToEnd, moveToLine
, moveToLineStart, moveToLineEnd
, wordForward, wordBackward
, frame
, showRepresentation
)
where
import Char
type Location = (Int, Int)
data EditBuffer = EditBuffer Int Location String deriving (Eq,Show)
emptyBuffer = EditBuffer 0 (0,0) ""
enterCommandMode :: EditBuffer -> EditBuffer
enterCommandMode = forceLocation
getBufferContents:: EditBuffer -> String
getBufferContents (EditBuffer _ _ contents) = contents
lineCount :: EditBuffer -> Int
lineCount (EditBuffer _ _ contents) = length . lines $ contents
insertChar :: Char -> EditBuffer -> EditBuffer
insertChar ch buffer@(EditBuffer topLine (x, y) contents)
| ch == '\n' = EditBuffer topLine (0, y+1) newContents
| otherwise = EditBuffer topLine (x+1, y) newContents
where newContents = before ++ [ch] ++ after
(before, after) = split buffer
deleteChar :: EditBuffer -> EditBuffer
deleteChar buffer@(EditBuffer topLine location@(x,y) contents)
| (currentLineLength buffer == 0) = buffer
| otherwise = satX 0 (EditBuffer topLine location newContents)
where newContents = before ++ (tail after)
(before, after) = split buffer
replaceChar :: Char -> EditBuffer -> EditBuffer
replaceChar replacementChar buffer@(EditBuffer topLine location contents) =
let newContents = map f . numberedElements $ contents
f (ch, pos) = if pos == (absPosition buffer) then replacementChar else ch
in EditBuffer topLine location newContents
insertLineAfter :: EditBuffer -> EditBuffer
insertLineAfter (EditBuffer topLine _ "") = EditBuffer topLine (0,1) "\n"
insertLineAfter (EditBuffer topLine (_,y) contents) = EditBuffer topLine (0,y+1) newContents
where newContents = unlines . map f . numberedLines $ contents
f (line, pos) = if pos == y then line ++ "\n" else line
deleteLine :: EditBuffer ->EditBuffer
deleteLine (EditBuffer topLine location@(_,y) contents) = forceLocation (EditBuffer topLine location newContents)
where newContents = unlines [ line | (line, pos) <- numberedLines contents, pos /= y]
moveLeft, moveRight, moveUp, moveDown :: EditBuffer -> EditBuffer
moveLeft = saturate (-1, 0)
moveRight = saturate ( 1, 0)
moveUp = saturate ( 0,-1)
moveDown = saturate ( 0, 1)
moveToHome :: EditBuffer -> EditBuffer
moveToHome (EditBuffer topLine _ contents) = EditBuffer topLine (0,0) contents
moveToEnd :: EditBuffer -> EditBuffer
moveToEnd = saturate (lastPos, lastPos)
where lastPos = (maxBound :: Int) - 1
moveToLine :: Int -> EditBuffer -> EditBuffer
moveToLine lineNumber (EditBuffer topLine (x,y) contents) =
forceLocation (EditBuffer topLine (x, lineNumber) contents)
moveToLineStart :: EditBuffer -> EditBuffer
moveToLineStart (EditBuffer topLine (_,y) contents) = EditBuffer topLine (0,y) contents
moveToLineEnd :: EditBuffer -> EditBuffer
moveToLineEnd buffer@(EditBuffer topLine (_,y) contents) =
satX 0 $ (EditBuffer topLine ((currentLineLength buffer), y) contents)
wordForward :: EditBuffer -> EditBuffer
wordForward buffer@(EditBuffer topLine _ contents) =
case dropSpaces . dropWord . drop (absPosition buffer) . numberedElements $ contents of
[] -> buffer
((_,pos) : _) -> EditBuffer topLine (locationFromPosition pos contents) contents
wordBackward :: EditBuffer -> EditBuffer
wordBackward buffer@(EditBuffer topLine _ contents) =
case dropWord . dropSpaces . reverse . take (absPosition buffer) . numberedElements $ contents of
[] -> EditBuffer topLine (locationFromPosition 0 contents) contents
((_,pos) : _) -> EditBuffer topLine (locationFromPosition (pos+1) contents) contents
frame :: EditBuffer -> EditBuffer
frame buffer@(EditBuffer topLine (x,y) contents)
| y > topLine + 40 = EditBuffer (y - 40) (x,y) contents
| y < topLine = EditBuffer y (x,y) contents
| otherwise = buffer
showRepresentation :: EditBuffer -> String
showRepresentation (EditBuffer topLine location contents) =
show topLine ++ " " ++ show location ++ " " ++ show contents
forceLocation = saturate (0,0)
currentLine :: EditBuffer -> String
currentLine (EditBuffer _ _ "") = ""
currentLine buffer@(EditBuffer _ (_, y) contents)
| (y < 0) || (y >= (lineCount buffer)) = ""
| otherwise = (lines contents) !! y
currentLineLength :: EditBuffer -> Int
currentLineLength = length . currentLine
split :: EditBuffer -> (String,String)
split buffer@(EditBuffer _ _ contents) = splitAt point contents
where point = absPosition buffer
absPosition :: EditBuffer -> Int
absPosition (EditBuffer _ (x, y) contents) =
(x+) . length . unlines . take y . lines $ contents
locationFromPosition :: Int -> String -> Location
locationFromPosition pos contents =
let foreLines = init . lines . take (pos + 1) $ contents
x = pos - (length $ unlines foreLines)
y = length foreLines
in (x, y)
isPunct :: Char -> Bool
isPunct ch = isAscii ch && not (isAlphaNum ch) && not (isSpace ch) && not (isControl ch)
dropWord :: [(Char,a)] -> [(Char,a)]
dropWord [] = []
dropWord all@((ch,_):_)
| isPunct ch = dropPuncts all
| isAlphaNum ch = dropAlphaNums all
| otherwise = all
dropPuncts, dropSpaces, dropAlphaNums :: [(Char,a)] -> [(Char,a)]
dropPuncts = dropInNumbered isPunct
dropSpaces = dropInNumbered isSpace
dropAlphaNums = dropInNumbered isAlphaNum
dropInNumbered :: (Char -> Bool) -> [(Char,a)] -> [(Char,a)]
dropInNumbered f = dropWhile (\(ch,_) -> f ch)
saturate :: (Int,Int) -> EditBuffer -> EditBuffer
saturate (adjX,adjY) = satX adjX . satY adjY
satX :: Int -> EditBuffer -> EditBuffer
satX adjX buffer@(EditBuffer topLine (x,y) contents) =
EditBuffer topLine (saturateValue (currentLineLength buffer) (x + adjX), y) contents
satY :: Int -> EditBuffer -> EditBuffer
satY adjY buffer@(EditBuffer topLine (x,y) contents) =
EditBuffer topLine (x, saturateValue (lineCount buffer) (y + adjY)) contents
saturateValue :: Int -> Int -> Int
saturateValue bound value
| bound <= 1 = 0
| value <= 0 = 0
| value >= bound = bound - 1
| otherwise = value
numberedElements :: [a] -> [(a,Int)]
numberedElements = (flip zip) [0..]
numberedLines :: String -> [(String,Int)]
numberedLines = numberedElements . lines