/*++ Copyright (c) 1999-2000 Microsoft Corporation Module Name: docwin.cpp Abstract: This module contains the code for the new doc windows. --*/ #include "precomp.hxx" #pragma hdrstop #include ULONG g_TabWidth = 32; BOOL g_DisasmActivateSource; // // // DOCWIN_DATA::DOCWIN_DATA() // State buffer isn't currently used. : EDITWIN_DATA(256) { m_enumType = DOC_WINDOW; ZeroMemory(m_szFoundFile, _tsizeof(m_szFoundFile)); ZeroMemory(m_szSymFile, _tsizeof(m_szSymFile)); ZeroMemory(&m_LastWriteTime, sizeof(m_LastWriteTime)); m_FindSel.cpMin = 1; m_FindSel.cpMax = 0; m_FindFlags = 0; } void DOCWIN_DATA::Validate() { EDITWIN_DATA::Validate(); Assert(DOC_WINDOW == m_enumType); } BOOL DOCWIN_DATA::CanGotoLine(void) { return m_TextLines > 0; } void DOCWIN_DATA::GotoLine(ULONG Line) { CHARRANGE Sel; Sel.cpMin = (LONG)SendMessage(m_hwndChild, EM_LINEINDEX, Line - 1, 0); Sel.cpMax = Sel.cpMin; SendMessage(m_hwndChild, EM_EXSETSEL, 0, (LPARAM)&Sel); } void DOCWIN_DATA::Find(PTSTR Text, ULONG Flags) { RicheditFind(m_hwndChild, Text, Flags, &m_FindSel, &m_FindFlags, TRUE); } BOOL DOCWIN_DATA::CodeExprAtCaret(PSTR Expr, PULONG64 Offset) { LRESULT LineChar; LONG Line; LineChar = SendMessage(m_hwndChild, EM_LINEINDEX, -1, 0); Line = (LONG)SendMessage(m_hwndChild, EM_EXLINEFROMCHAR, 0, LineChar); if (Line < 0) { return FALSE; } // Convert to one-based. Line++; if (Offset != NULL) { *Offset = DEBUG_INVALID_OFFSET; } if (Expr == NULL) { // Caller is just checking whether it's possible // to get an expression or not, such as the // menu enable code. This code always considers // it possible since it can't know for sure without // a full symbol check. return TRUE; } // // First attempt to resolve the source line using currently // loaded symbols. This is done directly from the UI // thread for synchronous behavior. The assumption is // that turning off symbol loads will limit the execution // time to something reasonably quick. // DEBUG_VALUE Val; HRESULT Status; sprintf(Expr, "`%s:%d+`", m_pszSymFile, Line); Status = g_pUiControl->Evaluate(Expr, DEBUG_VALUE_INT64, &Val, NULL); // Don't preserve the nqualified option in the actual // expression returned as it's just a temporary override. sprintf(Expr, "`%s:%d+`", m_pszSymFile, Line); if (Status == S_OK) { if (Offset != NULL) { *Offset = Val.I64; } return TRUE; } ULONG SymOpts; if (g_pUiSymbols->GetSymbolOptions(&SymOpts) == S_OK && (SymOpts & SYMOPT_NO_UNQUALIFIED_LOADS)) { // The user isn't allowing unqualified loads so // further searches won't help. return FALSE; } // We weren't able to resolve the expression with the // existing symbols so we'll need to do a full search. // This can be very expensive, so allow the user to cancel. if (!g_QuietMode) { int Mode = QuestionBox(STR_Unresolved_Source_Expr, MB_YESNOCANCEL); if (Mode == IDCANCEL) { return FALSE; } else if (Mode == IDYES) { if (g_pUiControl->Evaluate(Expr, DEBUG_VALUE_INT64, &Val, NULL) == S_OK) { if (Offset != NULL) { *Offset = Val.I64; } return TRUE; } else { return FALSE; } } } // Let the expression go without trying to further resolve it. return TRUE; } void DOCWIN_DATA::ToggleBpAtCaret(void) { LRESULT LineChar; LONG Line; LineChar = SendMessage(m_hwndChild, EM_LINEINDEX, -1, 0); Line = (LONG)SendMessage(m_hwndChild, EM_EXLINEFROMCHAR, 0, LineChar); if (Line < 0) { return; } // If we have a breakpoint on this line remove it. EDIT_HIGHLIGHT* Hl = GetLineHighlighting(Line); if (Hl != NULL && (Hl->Flags & EHL_ANY_BP)) { PrintStringCommand(UIC_SILENT_EXECUTE, "bc %d", (ULONG)Hl->Data); return; } // // No breakpoint exists so add a new one. // char CodeExpr[MAX_OFFSET_EXPR]; ULONG64 Offset; if (!CodeExprAtCaret(CodeExpr, &Offset)) { MessageBeep(0); ErrorBox(NULL, 0, ERR_No_Code_For_File_Line); } else { if (Offset != DEBUG_INVALID_OFFSET) { char SymName[MAX_OFFSET_EXPR]; ULONG64 Disp; // Check and see whether this offset maps // exactly to a symbol. If it does, use // the symbol name to be more robust in the // face of source changes. // Symbols should be loaded at this point since // we just used them to resolve the source // expression that produced Offset, so we // can safely do this on the UI thread. if (g_pUiSymbols->GetNameByOffset(Offset, SymName, sizeof(SymName), NULL, &Disp) == S_OK && Disp == 0) { strcpy(CodeExpr, SymName); } } PrintStringCommand(UIC_SILENT_EXECUTE, "bu %s", CodeExpr); } } BOOL DOCWIN_DATA::OnCreate(void) { if (!EDITWIN_DATA::OnCreate()) { return FALSE; } SendMessage(m_hwndChild, EM_SETEVENTMASK, 0, ENM_SELCHANGE | ENM_KEYEVENTS); SendMessage(m_hwndChild, EM_SETTABSTOPS, 1, (LPARAM)&g_TabWidth); return TRUE; } LRESULT DOCWIN_DATA::OnNotify(WPARAM Wpm, LPARAM Lpm) { SELCHANGE* SelChange = (SELCHANGE*)Lpm; if (SelChange->nmhdr.code == EN_SELCHANGE) { int Line = (int) SendMessage(m_hwndChild, EM_EXLINEFROMCHAR, 0, SelChange->chrg.cpMin); LRESULT LineFirst = SendMessage(m_hwndChild, EM_LINEINDEX, Line, 0); SetLineColumn_StatusBar(Line + 1, (int)(SelChange->chrg.cpMin - LineFirst) + 1); return 0; } return EDITWIN_DATA::OnNotify(Wpm, Lpm); } void DOCWIN_DATA::OnUpdate( UpdateType Type ) { if (Type == UPDATE_BP || Type == UPDATE_BUFFER || Type == UPDATE_END_SESSION) { UpdateBpMarks(); } else if (Type == UPDATE_START_SESSION) { if (m_szFoundFile[0] && CheckForFileChanges(m_szFoundFile, &m_LastWriteTime) == IDYES) { char Found[MAX_SOURCE_PATH], Sym[MAX_SOURCE_PATH]; // Save away filenames since they're copied over // on a successful load. strcpy(Found, m_szFoundFile); strcpy(Sym, m_szSymFile); if (!LoadFile(Found, Sym)) { PostMessage(g_hwndMDIClient, WM_MDIDESTROY, (WPARAM)m_Win, 0); } } } } ULONG DOCWIN_DATA::GetWorkspaceSize(void) { ULONG Len = EDITWIN_DATA::GetWorkspaceSize(); Len += _tcslen(m_szFoundFile) + 1; Len += _tcslen(m_szSymFile) + 1; return Len; } PUCHAR DOCWIN_DATA::SetWorkspace(PUCHAR Data) { PTSTR Str = (PTSTR)EDITWIN_DATA::SetWorkspace(Data); _tcscpy(Str, m_szFoundFile); Str += _tcslen(m_szFoundFile) + 1; _tcscpy(Str, m_szSymFile); Str += _tcslen(m_szSymFile) + 1; return (PUCHAR)Str; } PUCHAR DOCWIN_DATA::ApplyWorkspace1(PUCHAR Data, PUCHAR End) { PTSTR Found = (PTSTR)EDITWIN_DATA::ApplyWorkspace1(Data, End); PTSTR Sym = Found + _tcslen(Found) + 1; if (Found[0]) { if (FindDocWindowByFileName(Found, NULL, NULL) || !LoadFile(Found, Sym[0] ? Sym : NULL)) { PostMessage(g_hwndMDIClient, WM_MDIDESTROY, (WPARAM)m_Win, 0); } } return (PUCHAR)(Sym + _tcslen(Sym) + 1); } void DOCWIN_DATA::UpdateBpMarks(void) { if (m_TextLines == 0 || g_BpBuffer->UiLockForRead() != S_OK) { return; } SendMessage(m_hwndChild, WM_SETREDRAW, FALSE, 0); // Remove existing BP highlights. RemoveAllHighlights(EHL_ANY_BP); // // Highlight every line that matches a breakpoint. // BpBufferData* BpData = (BpBufferData*)g_BpBuffer->GetDataBuffer(); ULONG i; for (i = 0; i < g_BpCount; i++) { if (BpData[i].FileOffset) { PSTR FileSpace; ULONG Line; PSTR FileStop, MatchStop; ULONG HlFlags; FileSpace = (PSTR)g_BpBuffer->GetDataBuffer() + BpData[i].FileOffset; // Adjust to zero-based. Line = *(ULONG UNALIGNED *)FileSpace - 1; FileSpace += sizeof(Line); // If this document's file matches some suffix // of the breakpoint's file at the path component // level then do the highlight. This can result in // extra highlights for multiple files with the same // name but in different directories. That's a rare // enough problem to wait for somebody to complain // before trying to hack some better check up. if (SymMatchFileName(FileSpace, (PSTR)m_pszSymFile, &FileStop, &MatchStop) || *MatchStop == '\\' || *MatchStop == '/' || *MatchStop == ':') { if (BpData[i].Flags & DEBUG_BREAKPOINT_ENABLED) { HlFlags = EHL_ENABLED_BP; } else { HlFlags = EHL_DISABLED_BP; } EDIT_HIGHLIGHT* Hl = AddHighlight(Line, HlFlags); if (Hl != NULL) { Hl->Data = BpData[i].Id; } } } } UnlockStateBuffer(g_BpBuffer); SendMessage(m_hwndChild, WM_SETREDRAW, TRUE, 0); InvalidateRect(m_hwndChild, NULL, TRUE); } DWORD CALLBACK EditStreamCallback( DWORD_PTR dwFileHandle, // application-defined value LPBYTE pbBuff, // data buffer LONG cb, // number of bytes to read or write LONG *pcb // number of bytes transferred ) { HRESULT Status; PathFile* File = (PathFile*)dwFileHandle; if ((Status = File->Read(pbBuff, cb, (PDWORD)pcb)) != S_OK) { return Status; } // Edit out page-break characters (^L's) as richedit // gives them their own line which throws off line numbers. while (cb-- > 0) { if (*pbBuff == '\f') { *pbBuff = ' '; } pbBuff++; } return 0; // No error } BOOL DOCWIN_DATA::LoadFile( PCTSTR pszFoundFile, PCTSTR pszSymFile ) /*++ Returns TRUE - Success, file opened and loaded FALSE - Failure, file not loaded --*/ { Assert(pszFoundFile); BOOL bRet = TRUE; HCURSOR hcursor = NULL; EDITSTREAM editstr = {0}; PathFile *File = NULL; if ((OpenPathFile(pszFoundFile, 0, &File)) != S_OK) { ErrorBox(NULL, 0, ERR_File_Open, pszFoundFile); bRet = FALSE; goto exit; } // Store last write time to check for file changes. if (File->GetLastWriteTime(&m_LastWriteTime) != S_OK) { ZeroMemory(&m_LastWriteTime, sizeof(m_LastWriteTime)); } // Set the Hour glass cursor hcursor = SetCursor( LoadCursor(NULL, IDC_WAIT) ); // Select all of the text so that it will be replaced SendMessage(m_hwndChild, EM_SETSEL, 0, -1); // Put the text into the window editstr.dwCookie = (DWORD_PTR)File; editstr.pfnCallback = EditStreamCallback; SendMessage(m_hwndChild, EM_STREAMIN, SF_TEXT, (LPARAM) &editstr ); // Restore cursor SetCursor(hcursor); _tcsncpy(m_szFoundFile, pszFoundFile, _tsizeof(m_szFoundFile) - 1 ); m_szFoundFile[ _tsizeof(m_szFoundFile) - 1 ] = 0; if (pszSymFile != NULL && pszSymFile[0]) { _tcsncpy(m_szSymFile, pszSymFile, _tsizeof(m_szSymFile) - 1 ); m_szSymFile[ _tsizeof(m_szSymFile) - 1 ] = 0; m_pszSymFile = m_szSymFile; } else { // No symbol file information so just use the found filename. m_szSymFile[0] = 0; m_pszSymFile = strrchr(m_szFoundFile, '\\'); if (m_pszSymFile == NULL) { m_pszSymFile = strrchr(m_szFoundFile, '/'); if (m_pszSymFile == NULL) { m_pszSymFile = strrchr(m_szFoundFile, ':'); if (m_pszSymFile == NULL) { m_pszSymFile = m_szFoundFile - 1; } } } m_pszSymFile++; } SetWindowText(m_Win, m_szFoundFile); if (SendMessage(m_hwndChild, WM_GETTEXTLENGTH, 0, 0) == 0) { m_TextLines = 0; } else { m_TextLines = (ULONG)SendMessage(m_hwndChild, EM_GETLINECOUNT, 0, 0); } if (g_LineMarkers) { // Insert marker space before every line. for (ULONG i = 0; i < m_TextLines; i++) { CHARRANGE Ins; Ins.cpMin = (LONG)SendMessage(m_hwndChild, EM_LINEINDEX, i, 0); Ins.cpMax = Ins.cpMin; SendMessage(m_hwndChild, EM_EXSETSEL, 0, (LPARAM)&Ins); SendMessage(m_hwndChild, EM_REPLACESEL, FALSE, (LPARAM)" "); } } // Request that the engine update the line map for the file. UiRequestRead(); exit: delete File; return bRet; } BOOL SameFileName(PCSTR Name1, PCSTR Name2) { while (*Name1) { if (!(((*Name1 == '\\' || *Name1 == '/') && (*Name2 == '\\' || *Name2 == '/')) || toupper(*Name1) == toupper(*Name2))) { return FALSE; } Name1++; Name2++; } return *Name2 == 0; } BOOL FindDocWindowByFileName( IN PCTSTR pszFile, OPTIONAL HWND *phwnd, OPTIONAL PDOCWIN_DATA *ppDocWinData ) /*++ Returns TRUE - If the window is currently open. FALSE - Not currently open. --*/ { Assert(pszFile); PLIST_ENTRY Entry; PDOCWIN_DATA pTmp; Entry = g_ActiveWin.Flink; while (Entry != &g_ActiveWin) { pTmp = (PDOCWIN_DATA)ACTIVE_WIN_ENTRY(Entry); if ( pTmp->m_enumType == DOC_WINDOW && SameFileName(pTmp->m_szFoundFile, pszFile) ) { if (ppDocWinData) { *ppDocWinData = pTmp; } if (phwnd) { *phwnd = pTmp->m_Win; } return TRUE; } Entry = Entry->Flink; } return FALSE; } BOOL OpenOrActivateFile(PCSTR FoundFile, PCSTR SymFile, ULONG Line, BOOL Activate, BOOL UserActivated) { HWND hwndDoc = NULL; PDOCWIN_DATA pDoc; BOOL Activated = FALSE; if ( FindDocWindowByFileName( FoundFile, &hwndDoc, &pDoc) ) { if (Activate) { // Found it. Now activate it. ActivateMDIChild(hwndDoc, UserActivated); Activated = TRUE; } } else { hwndDoc = NewDoc_CreateWindow(g_hwndMDIClient); if (hwndDoc == NULL) { return FALSE; } pDoc = GetDocWinData(hwndDoc); Assert(pDoc); if (!pDoc->LoadFile(FoundFile, SymFile)) { DestroyWindow(pDoc->m_Win); return FALSE; } Activated = TRUE; } // Success. Now highlight the line. pDoc->SetCurrentLineHighlight(Line); return Activated; } void UpdateCodeDisplay( ULONG64 Ip, PCSTR FoundFile, PCSTR SymFile, ULONG Line, BOOL UserActivated ) { // Update the disassembly window if there's one // active or there's no source information. BOOL Activated = FALSE; HWND hwndDisasm = GetDisasmHwnd(); if (hwndDisasm == NULL && FoundFile == NULL && (g_WinOptions & WOPT_AUTO_DISASM)) { // No disassembly window around and no source so create one. hwndDisasm = NewDisasm_CreateWindow(g_hwndMDIClient); } if (hwndDisasm != NULL) { PDISASMWIN_DATA pDis = GetDisasmWinData(hwndDisasm); Assert(pDis); pDis->SetCurInstr(Ip); } if (FoundFile != NULL) { // // We now know the file name and line number. Either // it's open or we open it. // Activated = OpenOrActivateFile(FoundFile, SymFile, Line, GetSrcMode_StatusBar() || g_DisasmActivateSource, UserActivated); } else { // No source file was found so make sure no // doc windows have a highlight. EDITWIN_DATA::RemoveActiveWinHighlights(1 << DOC_WINDOW, EHL_CURRENT_LINE); } if ((!Activated || !GetSrcMode_StatusBar()) && hwndDisasm != NULL) { // No window has been activated yet so fall back // on activating the disassembly window. ActivateMDIChild(hwndDisasm, UserActivated); } } void SetTabWidth(ULONG TabWidth) { PLIST_ENTRY Entry; PDOCWIN_DATA DocData; g_TabWidth = TabWidth; if (g_Workspace != NULL) { g_Workspace->SetUlong(WSP_GLOBAL_TAB_WIDTH, TabWidth); } Entry = g_ActiveWin.Flink; while (Entry != &g_ActiveWin) { DocData = (PDOCWIN_DATA)ACTIVE_WIN_ENTRY(Entry); if (DocData->m_enumType == DOC_WINDOW) { SendMessage(DocData->m_hwndChild, EM_SETTABSTOPS, 1, (LPARAM)&g_TabWidth); } Entry = Entry->Flink; } }