#appModules/devenv.py #A part of NonVisual Desktop Access (NVDA) #This file is covered by the GNU General Public License. #See the file COPYING for more details. #Copyright (C) 2010 Soronel Haetir # # Suggestions from James Teh have been used. # # Visual Studio 2005/2008 support for NVDA. # I believe this code should work for VS2002/2003 as well but have no way of testing that. # I have confirmed this script requires at least Visual Studio Standard, as the Express editions # don't register themselves with the running object table. # I have tried several means of getting around this, so far without success. # # I started with revision 3493 of the main NVDA branch for this work. # # !!! IMPORTANT !!! # # I had to modify many of the members of IVsTextManager and IVsTextView to cut down on dependencies. # Specifically any interface pointers other than IVsTextView have been changed to IUnknown # Also, many structure and enumerations have been replaced with c_int. # # If NVDA becomes more dependant on the Visual Studio SDK interfaces the embedded wrappers # should be dropped in favor of the type library. # # !!! END OF IMPORTANT INFORMATION !!! # # The Visual Studio 2008 SDK is required if you wish # to generate python type wrappers. It can be downloaded at: # http://www.microsoft.com/downloads/details.aspx?familyid=59EC6EC3-4273-48A3-BA25-DC925A45584D&displaylang=en # Use the MIDL compiler to build textmgr.tlb. # From \Program Files\Microsoft Visual Studio 2008 SDK\VisualStudioIntegration\Common\IDL: # midl /I ..\inc textmgr.idl # and then copy the resulting typelib to your sources\typelibs directory. # import ctypes import objbase from comtypes import IUnknown, IServiceProvider , GUID, COMMETHOD, HRESULT, BSTR from ctypes import POINTER, c_int, c_short, c_ushort, c_ulong import comtypes.client.dynamic from comtypes.automation import IDispatch from logHandler import log import textInfos.offsets from NVDAObjects.behaviors import EditableTextWithoutAutoSelectDetection from NVDAObjects.window import Window from NVDAObjects.window import DisplayModelEditableText import appModuleHandler # # A few helpful constants # VsRootWindowClassName="wndclass_desked_gsk" VsTextEditPaneClassName="VsTextEditPane" SVsTextManager = GUID('{F5E7E71D-1401-11D1-883B-0000F87579D2}') VsVersion_None = 0 VsVersion_2002 = 1 VsVersion_2003 = 2 VsVersion_2005 = 3 VsVersion_2008 = 4 # Possible values of the VS .Type property of VS windows. # According to the docs this property should not be used but I have not been able to determine all of the needed values # of the .Kind property which is the suggested alternative. # # I don't have a type library or header defining the VsWindowType enumeration so only .Type values # I've actually encountered are defined. # Known missing values are: # CodeWindow, Designer, Browser, Watch, Locals, # SolutionExplorer, Properties, Find, FindReplace, Toolbox, LinkedWindowFrame, MainWindow, Preview, # ColorPalettte, ToolWindowTaskList, Autos, CallStack, Threads, DocumentOutline, RunningDocuments # Most of these host controls which should hopefully be the "real" window by the time any text needs to be rendered. VsWindowTypeCommand = 15 VsWindowTypeDocument = 16 VsWindowTypeOutput = 17 # Scroll bar selector SB_HORZ = 0 SB_VERT = 1 class AppModule(appModuleHandler.AppModule): def chooseNVDAObjectOverlayClasses(self, obj, clsList): # Only use this overlay class if the top level automation object for the IDE can be retrieved, # as it will not work otherwise. if obj.windowClassName == VsTextEditPaneClassName and self._getDTE(): try: clsList.remove(DisplayModelEditableText) except ValueError: pass clsList.insert(0, VsTextEditPane) def _getDTE(self): # Return the already fetched instance if there is one. try: if self._DTE: return self._DTE except AttributeError: pass # Retrieve and cache the top level automation object for the IDE DTEVersion = VsVersion_None bctx = objbase.CreateBindCtx() ROT = objbase.GetRunningObjectTable() for mon in ROT: # Test for the strings Visual Studio may have registered with. displayName = mon.GetDisplayName(bctx, None) if "!VisualStudio.DTE.9.0:%d"%self.processID==displayName: DTEVersion=VsVersion_2008 elif "!VisualStudio.DTE.8.0:%d"%self.processID==displayName: DTEVersion = VsVersion_2005 elif "!VisualStudio.DTE.7.1:%d"%self.processID==displayName: DTEVersion = VsVersion_2003 elif "!VisualStudio.DTE:%d"%self.processID==displayName: DTEVersion = VsVersion_2002 if DTEVersion != VsVersion_None: self._DTEVersion = DTEVersion self._DTE = comtypes.client.dynamic.Dispatch(ROT.GetObject(mon).QueryInterface(IDispatch)) break else: # None found. log.debugWarning("No top level automation object found") self._DTE = None self._DTEVersion = VsVersion_None # Loop has completed return self._DTE def _getTextManager(self): try: if self._textManager: return self._textManager except AttributeError: pass serviceProvider = self._getDTE().QueryInterface(comtypes.IServiceProvider) self._textManager = serviceProvider.QueryService(SVsTextManager, IVsTextManager) return self._textManager class VsTextEditPaneTextInfo(textInfos.offsets.OffsetsTextInfo): def _InformUnsupportedWindowType(self,type): log.error("An unsupported window type `%d' was encountered, please inform the NVDA development team." %type) raise NotImplementedError def _getSelectionObject(self): Selection = None if self._window.Type == VsWindowTypeDocument: Selection = self._window.Selection elif self._window.Type == VsWindowTypeOutput: Selection = self._window.Object.ActivePane.TextDocument.Selection elif self._window.Type==VsWindowTypeCommand: Selection = self._window.Object.TextDocument.Selection else: self._InformUnsupportedWindowType(self._window.Type) return Selection def _createEditPoint(self): return self._getSelectionObject().ActivePoint.CreateEditPoint() def _getOffsetFromPoint(self,x,y): yMinUnit, yMaxUnit, yVisible, yFirstVisible = self._textView.GetScrollInfo(SB_VERT) hMinUnit, hMaxUnit, hVisible, hFirstVisible = self._textView.GetScrollInfo(SB_HORZ) # These should probably be cached as they are fairly unlikely to change, but ... lineHeight = self._textView.GetLineHeight() charWidth = self._window.Width / hVisible offsetLine = (y - self._window.Top) / lineHeight + yFirstVisible offsetChar = (x - self._window.Left) / charWidth + hFirstVisible return self._textView.GetNearestPosition(offsetLine, offsetChar)[0] def __init__(self, obj, position): self._window = obj._window self._textView = obj._textView super(VsTextEditPaneTextInfo, self).__init__(obj, position) def _getCaretOffset(self): return self._createEditPoint().AbsoluteCharOffset def _setCaretOffset(self,offset): self._getSelectionObject().MoveToAbsoluteOffset(offset) def _setSelectionOffsets(self,start,end): Selection = self._getSelectionObject() Selection.MoveToAbsoluteOffset(start) Selection.MoveToAbsoluteOffset(end,True) def _getSelectionOffsets(self): selection = self._getSelectionObject() startPos = selection.ActivePoint.CreateEditPoint().AbsoluteCharOffset - 1 endPos = selection.AnchorPoint.CreateEditPoint().AbsoluteCharOffset - 1 return (startPos,endPos) def _getTextRange(self,start,end): editPointStart = self._createEditPoint() editPointStart.StartOfDocument() if start: editPointStart.MoveToAbsoluteOffset(start) else: start = 1 return editPointStart.GetText(end-start) def _getWordOffsets(self,startOffset): editPointStart = self._createEditPoint() editPointEnd = editPointStart.CreateEditPoint() editPointEnd.WordRight() return editPointStart.AbsoluteCharOffset,editPointEnd.AbsoluteCharOffset def _getLineOffsets(self,offset): editPointStart = self._createEditPoint() editPointStart.MoveToAbsoluteOffset(offset) editPointStart.StartOfLine() editPointEnd = editPointStart.CreateEditPoint() editPointEnd.EndOfLine() return (editPointStart.AbsoluteCharOffset,editPointEnd.AbsoluteCharOffset) def _getLineNumFromOffset(self,offset): editPoint = self._createEditPoint() editPoint.MoveToAbsoluteOffset(offset) return editPoint.Line def _getStoryLength(self): editPoint = self._createEditPoint() editPoint.EndOfDocument() return editPoint.AbsoluteCharOffset class VsTextEditPane(EditableTextWithoutAutoSelectDetection,Window): TextInfo = VsTextEditPaneTextInfo def initOverlayClass(self): self._window = self.appModule._getDTE().ActiveWindow self.location = (self._window.Top,self._window.Left,self._window.Width,self._window.Height) self._textView = self.appModule._getTextManager().GetActiveView(True, None) def event_valueChange(self): pass class IVsTextView(IUnknown): _case_insensitive_ = True _iid_ = GUID('{BB23A14B-7C61-469A-9890-A95648CED5E6}') _idlflags_ = [] class IVsTextManager(comtypes.IUnknown): _case_insensitive_ = True _iid_ = GUID('{909F83E3-B3FC-4BBF-8820-64378744B39B}') _idlflags_ = [] IVsTextManager._methods_ = [ COMMETHOD([], HRESULT, 'RegisterView', ( ['in'], POINTER(IVsTextView), 'pView' ), ( ['in'], POINTER(IUnknown), 'pBuffer' )), COMMETHOD([], HRESULT, 'UnregisterView', ( ['in'], POINTER(IVsTextView), 'pView' )), COMMETHOD([], HRESULT, 'EnumViews', ( ['in'], POINTER(IUnknown), 'pBuffer' ), ( ['out'], POINTER(POINTER(IUnknown)), 'ppEnum' )), COMMETHOD([], HRESULT, 'CreateSelectionAction', ( ['in'], POINTER(IUnknown), 'pBuffer' ), ( ['out'], POINTER(POINTER(IUnknown)), 'ppAction' )), COMMETHOD([], HRESULT, 'MapFilenameToLanguageSID', ( ['in'], POINTER(c_ushort), 'pszFileName' ), ( ['out'], POINTER(GUID), 'pguidLangSID' )), COMMETHOD([], HRESULT, 'GetRegisteredMarkerTypeID', ( ['in'], POINTER(GUID), 'pguidMarker' ), ( ['out'], POINTER(c_int), 'piMarkerTypeID' )), COMMETHOD([], HRESULT, 'GetMarkerTypeInterface', ( ['in'], c_int, 'iMarkerTypeID' ), ( ['out'], POINTER(POINTER(IUnknown)), 'ppMarkerType' )), COMMETHOD([], HRESULT, 'GetMarkerTypeCount', ( ['out'], POINTER(c_int), 'piMarkerTypeCount' )), COMMETHOD([], HRESULT, 'GetActiveView', ( ['in'], c_int, 'fMustHaveFocus' ), ( ['in'], POINTER(IUnknown), 'pBuffer' ), ( ['out'], POINTER(POINTER(IVsTextView)), 'ppView' )), COMMETHOD([], HRESULT, 'GetUserPreferences', ( ['out'], POINTER(c_int), 'pViewPrefs' ), ( ['out'], POINTER(c_int), 'pFramePrefs' ), ( ['in', 'out'], POINTER(c_int), 'pLangPrefs' ), ( ['in', 'out'], POINTER(c_int), 'pColorPrefs' )), COMMETHOD([], HRESULT, 'SetUserPreferences', ( ['in'], POINTER(c_int), 'pViewPrefs' ), ( ['in'], POINTER(c_int), 'pFramePrefs' ), ( ['in'], POINTER(c_int), 'pLangPrefs' ), ( ['in'], POINTER(c_int), 'pColorPrefs' )), COMMETHOD([], HRESULT, 'SetFileChangeAdvise', ( ['in'], POINTER(c_ushort), 'pszFileName' ), ( ['in'], c_int, 'fStart' )), COMMETHOD([], HRESULT, 'SuspendFileChangeAdvise', ( ['in'], POINTER(c_ushort), 'pszFileName' ), ( ['in'], c_int, 'fSuspend' )), COMMETHOD([], HRESULT, 'NavigateToPosition', ( ['in'], POINTER(IUnknown), 'pBuffer' ), ( ['in'], POINTER(GUID), 'guidDocViewType' ), ( ['in'], c_int, 'iPos' ), ( ['in'], c_int, 'iLen' )), COMMETHOD([], HRESULT, 'NavigateToLineAndColumn', ( ['in'], POINTER(IUnknown), 'pBuffer' ), ( ['in'], POINTER(GUID), 'guidDocViewType' ), ( ['in'], c_int, 'iStartRow' ), ( ['in'], c_int, 'iStartIndex' ), ( ['in'], c_int, 'iEndRow' ), ( ['in'], c_int, 'iEndIndex' )), COMMETHOD([], HRESULT, 'GetBufferSccStatus', ( ['in'], POINTER(IUnknown), 'pBufData' ), ( ['out'], POINTER(c_int), 'pbNonEditable' )), COMMETHOD([], HRESULT, 'RegisterBuffer', ( ['in'], POINTER(IUnknown), 'pBuffer' )), COMMETHOD([], HRESULT, 'UnregisterBuffer', ( ['in'], POINTER(IUnknown), 'pBuffer' )), COMMETHOD([], HRESULT, 'EnumBuffers', ( ['out'], POINTER(POINTER(IUnknown)), 'ppEnum' )), COMMETHOD([], HRESULT, 'GetPerLanguagePreferences', ( ['out'], POINTER(c_int), 'pLangPrefs' )), COMMETHOD([], HRESULT, 'SetPerLanguagePreferences', ( ['in'], POINTER(c_int), 'pLangPrefs' )), COMMETHOD([], HRESULT, 'AttemptToCheckOutBufferFromScc', ( ['in'], POINTER(IUnknown), 'pBufData' ), ( ['out'], POINTER(c_int), 'pfCheckoutSucceeded' )), COMMETHOD([], HRESULT, 'GetShortcutManager', ( ['out'], POINTER(POINTER(IUnknown)), 'ppShortcutMgr' )), COMMETHOD([], HRESULT, 'RegisterIndependentView', ( ['in'], POINTER(IUnknown), 'punk' ), ( ['in'], POINTER(IUnknown), 'pBuffer' )), COMMETHOD([], HRESULT, 'UnregisterIndependentView', ( ['in'], POINTER(IUnknown), 'punk' ), ( ['in'], POINTER(IUnknown), 'pBuffer' )), COMMETHOD([], HRESULT, 'IgnoreNextFileChange', ( ['in'], POINTER(IUnknown), 'pBuffer' )), COMMETHOD([], HRESULT, 'AdjustFileChangeIgnoreCount', ( ['in'], POINTER(IUnknown), 'pBuffer' ), ( ['in'], c_int, 'fIgnore' )), COMMETHOD([], HRESULT, 'GetBufferSccStatus2', ( ['in'], POINTER(c_ushort), 'pszFileName' ), ( ['out'], POINTER(c_int), 'pbNonEditable' ), ( ['out'], POINTER(c_int), 'piStatusFlags' )), COMMETHOD([], HRESULT, 'AttemptToCheckOutBufferFromScc2', ( ['in'], POINTER(c_ushort), 'pszFileName' ), ( ['out'], POINTER(c_int), 'pfCheckoutSucceeded' ), ( ['out'], POINTER(c_int), 'piStatusFlags' )), COMMETHOD([], HRESULT, 'EnumLanguageServices', ( ['out'], POINTER(POINTER(IUnknown)), 'ppEnum' )), COMMETHOD([], HRESULT, 'EnumIndependentViews', ( ['in'], POINTER(IUnknown), 'pBuffer' ), ( ['out'], POINTER(POINTER(IUnknown)), 'ppEnum' )), ] IVsTextView._methods_ = [ COMMETHOD([], HRESULT, 'Initialize', ( ['in'], POINTER(IUnknown), 'pBuffer' ), ( ['in'], comtypes.wireHWND, 'hwndParent' ), ( ['in'], c_ulong, 'InitFlags' ), ( ['in'], POINTER(c_int), 'pInitView' )), COMMETHOD([], HRESULT, 'CloseView'), COMMETHOD([], HRESULT, 'GetCaretPos', ( ['out'], POINTER(c_int), 'piLine' ), ( ['out'], POINTER(c_int), 'piColumn' )), COMMETHOD([], HRESULT, 'SetCaretPos', ( ['in'], c_int, 'iLine' ), ( ['in'], c_int, 'iColumn' )), COMMETHOD([], HRESULT, 'GetSelectionSpan', ( ['out'], POINTER(c_int), 'pSpan' )), COMMETHOD([], HRESULT, 'GetSelection', ( ['out'], POINTER(c_int), 'piAnchorLine' ), ( ['out'], POINTER(c_int), 'piAnchorCol' ), ( ['out'], POINTER(c_int), 'piEndLine' ), ( ['out'], POINTER(c_int), 'piEndCol' )), COMMETHOD([], HRESULT, 'SetSelection', ( ['in'], c_int, 'iAnchorLine' ), ( ['in'], c_int, 'iAnchorCol' ), ( ['in'], c_int, 'iEndLine' ), ( ['in'], c_int, 'iEndCol' )), COMMETHOD([], c_int, 'GetSelectionMode'), COMMETHOD([], HRESULT, 'SetSelectionMode', ( ['in'], c_int, 'iSelMode' )), COMMETHOD([], HRESULT, 'ClearSelection', ( ['in'], c_int, 'fMoveToAnchor' )), COMMETHOD([], HRESULT, 'CenterLines', ( ['in'], c_int, 'iTopLine' ), ( ['in'], c_int, 'iCount' )), COMMETHOD([], HRESULT, 'GetSelectedText', ( ['out'], POINTER(BSTR), 'pbstrText' )), COMMETHOD([], HRESULT, 'GetSelectionDataObject', ( ['out'], POINTER(POINTER(IUnknown)), 'ppIDataObject' )), COMMETHOD([], HRESULT, 'GetTextStream', ( ['in'], c_int, 'iTopLine' ), ( ['in'], c_int, 'iTopCol' ), ( ['in'], c_int, 'iBottomLine' ), ( ['in'], c_int, 'iBottomCol' ), ( ['out'], POINTER(BSTR), 'pbstrText' )), COMMETHOD([], HRESULT, 'GetBuffer', ( ['out'], POINTER(POINTER(IUnknown)), 'ppBuffer' )), COMMETHOD([], HRESULT, 'SetBuffer', ( ['in'], POINTER(IUnknown), 'pBuffer' )), COMMETHOD([], comtypes.wireHWND, 'GetWindowHandle'), COMMETHOD([], HRESULT, 'GetScrollInfo', ( ['in'], c_int, 'iBar' ), ( ['out'], POINTER(c_int), 'piMinUnit' ), ( ['out'], POINTER(c_int), 'piMaxUnit' ), ( ['out'], POINTER(c_int), 'piVisibleUnits' ), ( ['out'], POINTER(c_int), 'piFirstVisibleUnit' )), COMMETHOD([], HRESULT, 'SetScrollPosition', ( ['in'], c_int, 'iBar' ), ( ['in'], c_int, 'iFirstVisibleUnit' )), COMMETHOD([], HRESULT, 'AddCommandFilter', ( ['in'], POINTER(IUnknown), 'pNewCmdTarg' ), ( ['out'], POINTER(POINTER(IUnknown)), 'ppNextCmdTarg' )), COMMETHOD([], HRESULT, 'RemoveCommandFilter', ( ['in'], POINTER(IUnknown), 'pCmdTarg' )), COMMETHOD([], HRESULT, 'UpdateCompletionStatus', ( ['in'], POINTER(IUnknown), 'pCompSet' ), ( ['in'], c_ulong, 'dwFlags' )), COMMETHOD([], HRESULT, 'UpdateTipWindow', ( ['in'], POINTER(IUnknown), 'pTipWindow' ), ( ['in'], c_ulong, 'dwFlags' )), COMMETHOD([], HRESULT, 'GetWordExtent', ( ['in'], c_int, 'iLine' ), ( ['in'], c_int, 'iCol' ), ( ['in'], c_ulong, 'dwFlags' ), ( ['out'], POINTER(c_int), 'pSpan' )), COMMETHOD([], HRESULT, 'RestrictViewRange', ( ['in'], c_int, 'iMinLine' ), ( ['in'], c_int, 'iMaxLine' ), ( ['in'], POINTER(IUnknown), 'pClient' )), COMMETHOD([], HRESULT, 'ReplaceTextOnLine', ( ['in'], c_int, 'iLine' ), ( ['in'], c_int, 'iStartCol' ), ( ['in'], c_int, 'iCharsToReplace' ), ( ['in'], POINTER(c_ushort), 'pszNewText' ), ( ['in'], c_int, 'iNewLen' )), COMMETHOD([], HRESULT, 'GetLineAndColumn', ( ['in'], c_int, 'iPos' ), ( ['out'], POINTER(c_int), 'piLine' ), ( ['out'], POINTER(c_int), 'piIndex' )), COMMETHOD([], HRESULT, 'GetNearestPosition', ( ['in'], c_int, 'iLine' ), ( ['in'], c_int, 'iCol' ), ( ['out'], POINTER(c_int), 'piPos' ), ( ['out'], POINTER(c_int), 'piVirtualSpaces' )), COMMETHOD([], HRESULT, 'UpdateViewFrameCaption'), COMMETHOD([], HRESULT, 'CenterColumns', ( ['in'], c_int, 'iLine' ), ( ['in'], c_int, 'iLeftCol' ), ( ['in'], c_int, 'iColCount' )), COMMETHOD([], HRESULT, 'EnsureSpanVisible', ( ['in'], c_int, 'span' )), COMMETHOD([], HRESULT, 'PositionCaretForEditing', ( ['in'], c_int, 'iLine' ), ( ['in'], c_int, 'cIndentLevels' )), COMMETHOD([], HRESULT, 'GetPointOfLineColumn', ( ['in'], c_int, 'iLine' ), ( ['in'], c_int, 'iCol' ), ( ['retval', 'out'], POINTER(ctypes.wintypes.tagPOINT), 'ppt' )), COMMETHOD([], HRESULT, 'GetLineHeight', ( ['retval', 'out'], POINTER(c_int), 'piLineHeight' )), COMMETHOD([], HRESULT, 'HighlightMatchingBrace', ( ['in'], c_ulong, 'dwFlags' ), ( ['in'], c_ulong, 'cSpans' ), ( ['in'], POINTER(c_int), 'rgBaseSpans' )), COMMETHOD([], HRESULT, 'SendExplicitFocus'), COMMETHOD([], HRESULT, 'SetTopLine', ( ['in'], c_int, 'iBaseLine' )), ]