scintilla.py
9.19 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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
import ctypes
import IAccessibleHandler
import speech
import textInfos.offsets
import winKernel
import winUser
import globalVars
import controlTypes
import config
from . import Window
from .. import NVDAObjectTextInfo
from ..behaviors import EditableTextWithAutoSelectDetection
import locale
import watchdog
import eventHandler
#Window messages
SCI_POSITIONFROMPOINT=2022
SCI_POINTXFROMPOSITION=2164
SCI_POINTYFROMPOSITION=2165
SCI_GETTEXTRANGE=2162
SCI_GETTEXT=2182
SCI_GETTEXTLENGTH=2183
SCI_GETLENGTH=2006
SCI_GETCURRENTPOS=2008
SCI_GETANCHOR=2009
SCI_GOTOPOS=2025
SCI_SETCURRENTPOS=2141
SCI_GETSELECTIONSTART=2143
SCI_GETSELECTIONEND=2145
SCI_SETSEL=2160
SCI_GETLINEENDPOSITION=2136
SCI_GETLINECOUNT=2154
SCI_LINEFROMPOSITION=2166
SCI_POSITIONFROMLINE=2167
SCI_LINELENGTH=2350
SCI_GETSTYLEAT=2010
SCI_STYLEGETFONT=2486
SCI_STYLEGETSIZE=2485
SCI_STYLEGETBOLD=2483
SCI_STYLEGETITALIC=2484
SCI_STYLEGETUNDERLINE=2488
SCI_WORDSTARTPOSITION=2266
SCI_WORDENDPOSITION=2267
SC_WRAP_NONE=0
SCI_GETWRAPMODE=2269
SCI_GETCODEPAGE=2137
SCI_POSITIONAFTER=2418
#constants
STYLE_DEFAULT=32
SC_CP_UTF8=65001
class CharacterRangeStruct(ctypes.Structure):
_fields_=[
('cpMin',ctypes.c_long),
('cpMax',ctypes.c_long),
]
class TextRangeStruct(ctypes.Structure):
_fields_=[
('chrg',CharacterRangeStruct),
('lpstrText',ctypes.c_char_p),
]
class ScintillaTextInfo(textInfos.offsets.OffsetsTextInfo):
def _getOffsetFromPoint(self,x,y):
return watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_POSITIONFROMPOINT,x,y)
def _getPointFromOffset(self,offset):
point=textInfos.Point(
watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_POINTXFROMPOSITION,None,offset),
watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_POINTYFROMPOSITION,None,offset)
)
if point.x and point.y:
return point
else:
raise NotImplementedError
def _getFormatFieldAndOffsets(self,offset,formatConfig,calculateOffsets=True):
style=watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_GETSTYLEAT,offset,0)
if calculateOffsets:
#we need to manually see how far the style goes, limit to line
lineStart,lineEnd=self._getLineOffsets(offset)
startOffset=offset
while startOffset>lineStart:
curStyle=watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_GETSTYLEAT,startOffset-1,0)
if curStyle==style:
startOffset-=1
else:
break
endOffset=offset+1
while endOffset<lineEnd:
curStyle=watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_GETSTYLEAT,endOffset,0)
if curStyle==style:
endOffset+=1
else:
break
else:
startOffset,endOffset=(self._startOffset,self._endOffset)
formatField=textInfos.FormatField()
if formatConfig["reportFontName"]:
#To get font name, We need to allocate memory with in Scintilla's process, and then copy it out
fontNameBuf=ctypes.create_string_buffer(32)
internalBuf=winKernel.virtualAllocEx(self.obj.processHandle,None,len(fontNameBuf),winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE)
try:
watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_STYLEGETFONT,style, internalBuf)
winKernel.readProcessMemory(self.obj.processHandle,internalBuf,fontNameBuf,len(fontNameBuf),None)
finally:
winKernel.virtualFreeEx(self.obj.processHandle,internalBuf,0,winKernel.MEM_RELEASE)
formatField["font-name"]=fontNameBuf.value
if formatConfig["reportFontSize"]:
formatField["font-size"]="%spt"%watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_STYLEGETSIZE,style,0)
if formatConfig["reportLineNumber"]:
formatField["line-number"]=self._getLineNumFromOffset(offset)+1
if formatConfig["reportFontAttributes"]:
formatField["bold"]=bool(watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_STYLEGETBOLD,style,0))
formatField["italic"]=bool(watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_STYLEGETITALIC,style,0))
formatField["underline"]=bool(watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_STYLEGETUNDERLINE,style,0))
return formatField,(startOffset,endOffset)
def _getCaretOffset(self):
return watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_GETCURRENTPOS,0,0)
def _setCaretOffset(self,offset):
watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_GOTOPOS,offset,0)
# #5678: A caret event sometimes doesn't get fired when we do this,
# so fake one just in case.
eventHandler.executeEvent("caret", self.obj)
def _getSelectionOffsets(self):
start=watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_GETSELECTIONSTART,0,0)
end=watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_GETSELECTIONEND,0,0)
return (start,end)
def _setSelectionOffsets(self,start,end):
watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_SETSEL,start,end)
def _getStoryText(self):
if not hasattr(self,'_storyText'):
storyLength=self._getStoryLength()
self._storyText=self._getTextRange(0,storyLength)
return self._storyText
def _getStoryLength(self):
if not hasattr(self,'_storyLength'):
self._storyLength=watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_GETTEXTLENGTH,0,0)
return self._storyLength
def _getLineCount(self):
return watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_GETLINECOUNT,0,0)
def _getTextRange(self,start,end):
bufLen=(end-start)+1
textRange=TextRangeStruct()
textRange.chrg.cpMin=start
textRange.chrg.cpMax=end
processHandle=self.obj.processHandle
internalBuf=winKernel.virtualAllocEx(processHandle,None,bufLen,winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE)
try:
textRange.lpstrText=internalBuf
internalTextRange=winKernel.virtualAllocEx(processHandle,None,ctypes.sizeof(textRange),winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE)
try:
winKernel.writeProcessMemory(processHandle,internalTextRange,ctypes.byref(textRange),ctypes.sizeof(textRange),None)
watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_GETTEXTRANGE,0,internalTextRange)
finally:
winKernel.virtualFreeEx(processHandle,internalTextRange,0,winKernel.MEM_RELEASE)
buf=ctypes.create_string_buffer(bufLen)
winKernel.readProcessMemory(processHandle,internalBuf,buf,bufLen,None)
finally:
winKernel.virtualFreeEx(processHandle,internalBuf,0,winKernel.MEM_RELEASE)
cp=watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_GETCODEPAGE,0,0)
if cp==SC_CP_UTF8:
return unicode(buf.value, errors="replace", encoding="utf-8")
else:
return unicode(buf.value, errors="replace", encoding=locale.getlocale()[1])
def _getWordOffsets(self,offset):
start=watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_WORDSTARTPOSITION,offset,0)
end=watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_WORDENDPOSITION,start,0)
if end<=offset:
start=end
end=watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_WORDENDPOSITION,offset,0)
return [start,end]
def _getLineNumFromOffset(self,offset):
return watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_LINEFROMPOSITION,offset,0)
def _getLineOffsets(self,offset):
if watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_GETWRAPMODE,None,None)!=SC_WRAP_NONE:
# Lines in Scintilla refer to document lines, not wrapped lines.
# There's no way to retrieve wrapped lines, so use screen coordinates.
y=watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_POINTYFROMPOSITION,None,offset)
top,left,width,height=self.obj.location
start = self._getOffsetFromPoint(0,y)
end=self._getOffsetFromPoint(width,y)
# If this line wraps to the next line,
# end is the first offset of the next line.
if watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_POINTYFROMPOSITION,None,end)==y:
# This is the end of the document line.
# Include the EOL characters in the returned offsets.
end=watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_POSITIONAFTER,end,None)
return (start,end)
line=watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_LINEFROMPOSITION,offset,0)
start=watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_POSITIONFROMLINE,line,0)
end=start+watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_LINELENGTH,line,0)
return (start,end)
def _getParagraphOffsets(self,offset):
return self._getLineOffsets(offset)
def _getCharacterOffsets(self,offset):
if offset>=self._getStoryLength(): return offset,offset+1
end=watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_POSITIONAFTER,offset,0)
start=offset
tempOffset=offset-1
while True:
start=watchdog.cancellableSendMessage(self.obj.windowHandle,SCI_POSITIONAFTER,tempOffset,0)
if start<end:
break
elif tempOffset==0:
start=tempOffset
break
else:
tempOffset-=1
return [start,end]
#The Scintilla NVDA object, inherists the generic MSAA NVDA object
class Scintilla(EditableTextWithAutoSelectDetection, Window):
TextInfo=ScintillaTextInfo
#The name of the object is gotten by the standard way of getting a window name, can't use MSAA name (since it contains all the text)
def _get_name(self):
return winUser.getWindowText(self.windowHandle)
#The role of the object should be editable text
def _get_role(self):
return controlTypes.ROLE_EDITABLETEXT
def _get_states(self):
states = super(Scintilla, self)._get_states()
# Scintilla controls are always multiline.
states.add(controlTypes.STATE_MULTILINE)
return states