Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1345 lines
35 KiB

  1. /*++
  2. Copyright (c) 1997-2001 Microsoft Corporation
  3. Module Name:
  4. cmdwin.cpp
  5. Abstract:
  6. New command window UI.
  7. --*/
  8. #include "precomp.hxx"
  9. #pragma hdrstop
  10. #define MAX_CMDWIN_LINES 30000
  11. // Minimum window pane size.
  12. #define MIN_PANE_SIZE 20
  13. BOOL g_AutoCmdScroll = TRUE;
  14. //
  15. //
  16. //
  17. CMDWIN_DATA::CMDWIN_DATA()
  18. // State buffer isn't currently used.
  19. : COMMONWIN_DATA(256)
  20. {
  21. m_enumType = CMD_WINDOW;
  22. m_bTrackingMouse = FALSE;
  23. m_nDividerPosition = 0;
  24. m_EditHeight = 0;
  25. m_hwndHistory = NULL;
  26. m_hwndEdit = NULL;
  27. m_bHistoryActive = FALSE;
  28. m_Prompt = NULL;
  29. m_PromptWidth = 0;
  30. m_OutputIndex = 0;
  31. m_OutputIndexAtEnd = TRUE;
  32. m_FindSel.cpMin = 1;
  33. m_FindSel.cpMax = 0;
  34. m_FindFlags = 0;
  35. }
  36. void
  37. CMDWIN_DATA::Validate()
  38. {
  39. COMMONWIN_DATA::Validate();
  40. Assert(CMD_WINDOW == m_enumType);
  41. Assert(m_hwndHistory);
  42. Assert(m_hwndEdit);
  43. }
  44. void
  45. CMDWIN_DATA::Find(PTSTR Text, ULONG Flags)
  46. {
  47. RicheditFind(m_hwndHistory, Text, Flags,
  48. &m_FindSel, &m_FindFlags, FALSE);
  49. }
  50. BOOL
  51. CMDWIN_DATA::OnCreate(void)
  52. {
  53. m_EditHeight = 3 * m_Font->Metrics.tmHeight / 2;
  54. m_nDividerPosition = m_Size.cy - m_EditHeight;
  55. m_hwndHistory = CreateWindowEx(
  56. WS_EX_CLIENTEDGE, // Extended style
  57. RICHEDIT_CLASS, // class name
  58. NULL, // title
  59. WS_CLIPSIBLINGS
  60. | WS_CHILD | WS_VISIBLE
  61. | WS_HSCROLL | WS_VSCROLL
  62. | ES_AUTOHSCROLL | ES_AUTOVSCROLL
  63. | ES_NOHIDESEL
  64. | ES_MULTILINE | ES_READONLY, // style
  65. 0, // x
  66. 0, // y
  67. 100, // width
  68. 100, // height
  69. m_Win, // parent
  70. (HMENU) IDC_RICHEDIT_CMD_HISTORY, // control id
  71. g_hInst, // hInstance
  72. NULL); // user defined data
  73. if ( !m_hwndHistory )
  74. {
  75. return FALSE;
  76. }
  77. m_PromptWidth = 4 * m_Font->Metrics.tmAveCharWidth;
  78. m_Prompt = CreateWindowEx(
  79. WS_EX_CLIENTEDGE, // Extended style
  80. "STATIC", // class name
  81. "", // title
  82. WS_CLIPSIBLINGS
  83. | WS_CHILD | WS_VISIBLE, // style
  84. 0, // x
  85. 100, // y
  86. m_PromptWidth, // width
  87. 100, // height
  88. m_Win, // parent
  89. (HMENU) IDC_STATIC, // control id
  90. g_hInst, // hInstance
  91. NULL); // user defined data
  92. if ( m_Prompt == NULL )
  93. {
  94. return FALSE;
  95. }
  96. m_hwndEdit = CreateWindowEx(
  97. WS_EX_CLIENTEDGE, // Extended style
  98. RICHEDIT_CLASS, // class name
  99. NULL, // title
  100. WS_CLIPSIBLINGS
  101. | WS_CHILD | WS_VISIBLE
  102. | WS_VSCROLL | ES_AUTOVSCROLL
  103. | ES_NOHIDESEL
  104. | ES_MULTILINE, // style
  105. m_PromptWidth, // x
  106. 100, // y
  107. 100, // width
  108. 100, // height
  109. m_Win, // parent
  110. (HMENU) IDC_RICHEDIT_CMD_EDIT, // control id
  111. g_hInst, // hInstance
  112. NULL); // user defined data
  113. if ( !m_hwndEdit )
  114. {
  115. return FALSE;
  116. }
  117. SetFont( FONT_FIXED );
  118. // Tell the edit control we want notification of keyboard input
  119. // so that we can automatically set focus to the edit window.
  120. SendMessage(m_hwndHistory, EM_SETEVENTMASK, 0, ENM_KEYEVENTS |
  121. ENM_MOUSEEVENTS);
  122. // Tell the edit controls, that we want notification of keyboard input
  123. // This is so we can process the enter key, and then send that text into
  124. // the History window.
  125. SendMessage(m_hwndEdit, EM_SETEVENTMASK, 0, ENM_KEYEVENTS |
  126. ENM_MOUSEEVENTS);
  127. return TRUE;
  128. }
  129. void
  130. CMDWIN_DATA::SetFont(ULONG FontIndex)
  131. {
  132. COMMONWIN_DATA::SetFont(FontIndex);
  133. SendMessage(m_hwndHistory, WM_SETFONT, (WPARAM)m_Font->Font, TRUE);
  134. SendMessage(m_hwndEdit, WM_SETFONT, (WPARAM)m_Font->Font, TRUE);
  135. SendMessage(m_Prompt, WM_SETFONT, (WPARAM)m_Font->Font, TRUE);
  136. }
  137. BOOL
  138. CMDWIN_DATA::CanCopy()
  139. {
  140. HWND hwnd = m_bHistoryActive ? m_hwndHistory : m_hwndEdit;
  141. CHARRANGE chrg;
  142. SendMessage(hwnd, EM_EXGETSEL, 0, (LPARAM)&chrg);
  143. return chrg.cpMin != chrg.cpMax;
  144. }
  145. BOOL
  146. CMDWIN_DATA::CanCut()
  147. {
  148. return !m_bHistoryActive && CanCopy() &&
  149. (GetWindowLong(m_hwndEdit, GWL_STYLE) & ES_READONLY) == 0;
  150. }
  151. BOOL
  152. CMDWIN_DATA::CanPaste()
  153. {
  154. return !m_bHistoryActive
  155. && SendMessage(m_hwndEdit, EM_CANPASTE, CF_TEXT, 0);
  156. }
  157. void
  158. CMDWIN_DATA::Copy()
  159. {
  160. HWND hwnd = m_bHistoryActive ? m_hwndHistory : m_hwndEdit;
  161. SendMessage(hwnd, WM_COPY, 0, 0);
  162. }
  163. void
  164. CMDWIN_DATA::Cut()
  165. {
  166. SendMessage(m_hwndEdit, WM_CUT, 0, 0);
  167. }
  168. void
  169. CMDWIN_DATA::Paste()
  170. {
  171. SendMessage(m_hwndEdit, WM_PASTE, 0, 0);
  172. }
  173. BOOL
  174. CMDWIN_DATA::CanSelectAll()
  175. {
  176. return m_bHistoryActive;
  177. }
  178. void
  179. CMDWIN_DATA::SelectAll()
  180. {
  181. CHARRANGE Sel;
  182. Sel.cpMin = 0;
  183. Sel.cpMax = INT_MAX;
  184. SendMessage(m_hwndHistory, EM_EXSETSEL, 0, (LPARAM)&Sel);
  185. }
  186. LRESULT
  187. CMDWIN_DATA::OnCommand(WPARAM wParam, LPARAM lParam)
  188. {
  189. int idEditCtrl = (int) LOWORD(wParam); // identifier of edit control
  190. WORD wNotifyCode = HIWORD(wParam); // notification code
  191. HWND hwndEditCtrl = (HWND) lParam; // handle of edit control
  192. switch (wNotifyCode)
  193. {
  194. case EN_SETFOCUS:
  195. m_bHistoryActive = IDC_RICHEDIT_CMD_HISTORY == idEditCtrl;
  196. return 0;
  197. }
  198. return 1;
  199. }
  200. void
  201. CMDWIN_DATA::OnSetFocus()
  202. {
  203. if (m_bHistoryActive)
  204. {
  205. ::SetFocus(m_hwndHistory);
  206. }
  207. else
  208. {
  209. ::SetFocus(m_hwndEdit);
  210. }
  211. }
  212. void
  213. CMDWIN_DATA::OnSize(void)
  214. {
  215. const int nDividerHeight = GetSystemMetrics(SM_CYEDGE);
  216. int nHistoryHeight;
  217. // Attempt to keep the input area the same size as it was
  218. // and modify the output area. If the input area would
  219. // take up more than half the window shrink it also.
  220. if (m_EditHeight > (int)m_Size.cy / 2)
  221. {
  222. m_EditHeight = m_Size.cy / 2;
  223. }
  224. else if (m_EditHeight < MIN_PANE_SIZE)
  225. {
  226. m_EditHeight = MIN_PANE_SIZE;
  227. }
  228. nHistoryHeight = m_Size.cy - m_EditHeight - nDividerHeight / 2;
  229. m_nDividerPosition = m_Size.cy - m_EditHeight;
  230. if ((int)m_PromptWidth > m_Size.cx / 2)
  231. {
  232. m_PromptWidth = m_Size.cx / 2;
  233. }
  234. MoveWindow(m_hwndHistory,
  235. 0,
  236. 0,
  237. m_Size.cx,
  238. nHistoryHeight,
  239. TRUE
  240. );
  241. MoveWindow(m_Prompt,
  242. 0,
  243. nHistoryHeight + nDividerHeight,
  244. m_PromptWidth,
  245. m_Size.cy - nHistoryHeight - nDividerHeight,
  246. TRUE
  247. );
  248. MoveWindow(m_hwndEdit,
  249. m_PromptWidth,
  250. nHistoryHeight + nDividerHeight,
  251. m_Size.cx - m_PromptWidth,
  252. m_Size.cy - nHistoryHeight - nDividerHeight,
  253. TRUE
  254. );
  255. if (g_AutoCmdScroll)
  256. {
  257. // Keep the caret visible in both windows.
  258. SendMessage(m_hwndHistory, EM_SCROLLCARET, 0, 0);
  259. SendMessage(m_hwndEdit, EM_SCROLLCARET, 0, 0);
  260. }
  261. }
  262. void
  263. CMDWIN_DATA::OnButtonDown(ULONG Button)
  264. {
  265. if (Button & MK_LBUTTON)
  266. {
  267. m_bTrackingMouse = TRUE;
  268. SetCapture(m_Win);
  269. }
  270. }
  271. void
  272. CMDWIN_DATA::OnButtonUp(ULONG Button)
  273. {
  274. if (Button & MK_LBUTTON)
  275. {
  276. if (m_bTrackingMouse)
  277. {
  278. m_bTrackingMouse = FALSE;
  279. ReleaseCapture();
  280. }
  281. }
  282. }
  283. void
  284. CMDWIN_DATA::OnMouseMove(ULONG Modifiers, ULONG X, ULONG Y)
  285. {
  286. if (MK_LBUTTON & Modifiers && m_bTrackingMouse)
  287. {
  288. // We are resizing the History & Edit Windows
  289. // Y position centered vertically around the cursor
  290. ULONG EdgeHeight = GetSystemMetrics(SM_CYEDGE);
  291. MoveDivider(Y - EdgeHeight / 2);
  292. }
  293. }
  294. LRESULT
  295. CMDWIN_DATA::OnNotify(WPARAM wParam, LPARAM lParam)
  296. {
  297. MSGFILTER * lpMsgFilter = (MSGFILTER *) lParam;
  298. if (EN_MSGFILTER != lpMsgFilter->nmhdr.code)
  299. {
  300. return 0;
  301. }
  302. if (WM_RBUTTONDOWN == lpMsgFilter->msg ||
  303. WM_RBUTTONDBLCLK == lpMsgFilter->msg)
  304. {
  305. // If there's a selection copy it to the clipboard
  306. // and clear it. Otherwise try to paste.
  307. if (CanCopy())
  308. {
  309. Copy();
  310. CHARRANGE Sel;
  311. HWND hwnd = m_bHistoryActive ? m_hwndHistory : m_hwndEdit;
  312. SendMessage(hwnd, EM_EXGETSEL, 0, (LPARAM)&Sel);
  313. Sel.cpMax = Sel.cpMin;
  314. SendMessage(hwnd, EM_EXSETSEL, 0, (LPARAM)&Sel);
  315. }
  316. else if (SendMessage(m_hwndEdit, EM_CANPASTE, CF_TEXT, 0))
  317. {
  318. SetFocus(m_hwndEdit);
  319. Paste();
  320. }
  321. // Ignore right-button events.
  322. return 1;
  323. }
  324. else if (lpMsgFilter->msg < WM_KEYFIRST || lpMsgFilter->msg > WM_KEYLAST)
  325. {
  326. // Process all non-key events.
  327. return 0;
  328. }
  329. else if (WM_SYSKEYDOWN == lpMsgFilter->msg ||
  330. WM_SYSKEYUP == lpMsgFilter->msg ||
  331. WM_SYSCHAR == lpMsgFilter->msg)
  332. {
  333. // Process menu operations though the default so
  334. // that the Alt-minus menu works.
  335. return 1;
  336. }
  337. // Allow tab to toggle between the windows.
  338. // Make sure that it isn't a Ctrl-Tab or Alt-Tab.
  339. if (WM_KEYUP == lpMsgFilter->msg && VK_TAB == lpMsgFilter->wParam &&
  340. GetKeyState(VK_CONTROL) >= 0 && GetKeyState(VK_MENU) >= 0)
  341. {
  342. HWND hwnd = m_bHistoryActive ? m_hwndEdit : m_hwndHistory;
  343. SetFocus(hwnd);
  344. return 1;
  345. }
  346. else if ((WM_KEYDOWN == lpMsgFilter->msg &&
  347. VK_TAB == lpMsgFilter->wParam) ||
  348. (WM_CHAR == lpMsgFilter->msg &&
  349. '\t' == lpMsgFilter->wParam))
  350. {
  351. return 1;
  352. }
  353. switch (wParam)
  354. {
  355. case IDC_RICHEDIT_CMD_HISTORY:
  356. // Ignore key-ups to ignore the tail end of
  357. // menu operations. The switch below will occur on
  358. // key-down anyway so key-up doesn't need to do it.
  359. if (WM_KEYUP == lpMsgFilter->msg)
  360. {
  361. return 0;
  362. }
  363. // Allow keyboard navigation in the history text.
  364. if (WM_KEYDOWN == lpMsgFilter->msg)
  365. {
  366. switch(lpMsgFilter->wParam)
  367. {
  368. case VK_LEFT:
  369. case VK_RIGHT:
  370. case VK_UP:
  371. case VK_DOWN:
  372. case VK_PRIOR:
  373. case VK_NEXT:
  374. case VK_HOME:
  375. case VK_END:
  376. case VK_SHIFT:
  377. case VK_CONTROL:
  378. return 0;
  379. }
  380. }
  381. // Forward key events to the edit window.
  382. SetFocus(m_hwndEdit);
  383. SendMessage(m_hwndEdit, lpMsgFilter->msg, lpMsgFilter->wParam,
  384. lpMsgFilter->lParam);
  385. return 1; // ignore
  386. case IDC_RICHEDIT_CMD_EDIT:
  387. // If the window isn't accepting input don't do history.
  388. if (GetWindowLong(m_hwndEdit, GWL_STYLE) & ES_READONLY)
  389. {
  390. return 1;
  391. }
  392. static HISTORY_LIST *pHistoryList = NULL;
  393. switch (lpMsgFilter->msg)
  394. {
  395. case WM_KEYDOWN:
  396. switch (lpMsgFilter->wParam)
  397. {
  398. default:
  399. return 0;
  400. case VK_RETURN:
  401. // Reset the history list
  402. pHistoryList = NULL;
  403. int nLen;
  404. TEXTRANGE TextRange;
  405. // Get length.
  406. // +1 we have to take into account the null terminator.
  407. nLen = GetWindowTextLength(lpMsgFilter->nmhdr.hwndFrom) +1;
  408. // Get everything
  409. TextRange.chrg.cpMin = 0;
  410. TextRange.chrg.cpMax = -1;
  411. TextRange.lpstrText = (PTSTR) calloc(nLen, sizeof(TCHAR));
  412. if (TextRange.lpstrText)
  413. {
  414. // Okay got the text
  415. GetWindowText(m_hwndEdit,
  416. TextRange.lpstrText,
  417. nLen
  418. );
  419. SetWindowText(m_hwndEdit,
  420. _T("")
  421. );
  422. CmdExecuteCmd(TextRange.lpstrText, UIC_CMD_INPUT);
  423. free(TextRange.lpstrText);
  424. }
  425. // ignore the event
  426. return 1;
  427. case VK_UP:
  428. case VK_DOWN:
  429. CHARRANGE End;
  430. LRESULT LineCount;
  431. End.cpMin = INT_MAX;
  432. End.cpMax = INT_MAX;
  433. if (IsListEmpty( (PLIST_ENTRY) &m_listHistory ))
  434. {
  435. return 0; // process
  436. }
  437. LineCount = SendMessage(m_hwndEdit, EM_GETLINECOUNT, 0, 0);
  438. if (LineCount != 1)
  439. {
  440. // If more than 1 line, then scroll through the text
  441. // unless at the top or bottom line.
  442. if (VK_UP == lpMsgFilter->wParam)
  443. {
  444. if (SendMessage(m_hwndEdit, EM_LINEINDEX, -1, 0) != 0)
  445. {
  446. return 0;
  447. }
  448. }
  449. else
  450. {
  451. if (SendMessage(m_hwndEdit, EM_LINEFROMCHAR, -1, 0) <
  452. LineCount - 1)
  453. {
  454. return 0;
  455. }
  456. }
  457. }
  458. if (NULL == pHistoryList)
  459. {
  460. // first time scrolling thru the list,
  461. // start at the beginning
  462. pHistoryList = (HISTORY_LIST *) m_listHistory.Flink;
  463. SetWindowText(m_hwndEdit, pHistoryList->m_psz);
  464. // Put the cursor at the end.
  465. SendMessage(m_hwndEdit, EM_EXSETSEL, 0, (LPARAM)&End);
  466. SendMessage(m_hwndEdit, EM_SCROLLCARET, 0, 0);
  467. return 1; // ignore
  468. }
  469. if (VK_UP == lpMsgFilter->wParam)
  470. {
  471. // up
  472. if (pHistoryList->Flink != (PLIST_ENTRY) &m_listHistory)
  473. {
  474. pHistoryList = (HISTORY_LIST *) pHistoryList->Flink;
  475. }
  476. else
  477. {
  478. return 0; // process
  479. }
  480. SetWindowText(m_hwndEdit, pHistoryList->m_psz);
  481. // Put the cursor at the end.
  482. SendMessage(m_hwndEdit, EM_EXSETSEL, 0, (LPARAM)&End);
  483. SendMessage(m_hwndEdit, EM_SCROLLCARET, 0, 0);
  484. return 1; // ignore
  485. }
  486. else
  487. {
  488. // down
  489. if (pHistoryList->Blink != (PLIST_ENTRY) &m_listHistory)
  490. {
  491. pHistoryList = (HISTORY_LIST *) pHistoryList->Blink;
  492. }
  493. else
  494. {
  495. return 0; // process
  496. }
  497. SetWindowText(m_hwndEdit, pHistoryList->m_psz);
  498. // Put the cursor at the end.
  499. SendMessage(m_hwndEdit, EM_EXSETSEL, 0, (LPARAM)&End);
  500. SendMessage(m_hwndEdit, EM_SCROLLCARET, 0, 0);
  501. return 1; // ignore
  502. }
  503. case VK_ESCAPE:
  504. // Clear the current command.
  505. SetWindowText(m_hwndEdit, "");
  506. // Reset the history list
  507. pHistoryList = NULL;
  508. return 1;
  509. }
  510. }
  511. // process the event
  512. return 0;
  513. }
  514. return 0;
  515. }
  516. void
  517. CMDWIN_DATA::OnUpdate(UpdateType Type)
  518. {
  519. PSTR Prompt = NULL;
  520. if (Type == UPDATE_EXEC ||
  521. Type == UPDATE_INPUT_REQUIRED)
  522. {
  523. if (Type == UPDATE_INPUT_REQUIRED ||
  524. g_ExecStatus == DEBUG_STATUS_BREAK)
  525. {
  526. SendMessage(m_hwndEdit, EM_SETBKGNDCOLOR, TRUE, 0);
  527. SendMessage(m_hwndEdit, EM_SETREADONLY, FALSE, 0);
  528. SetWindowText(m_hwndEdit, _T(""));
  529. //
  530. // If WinDBG is minized and we breakin, flash the window
  531. //
  532. if (IsIconic(g_hwndFrame) && g_FlashWindowEx != NULL)
  533. {
  534. FLASHWINFO FlashInfo = {sizeof(FLASHWINFO), g_hwndFrame,
  535. FLASHW_ALL | FLASHW_TIMERNOFG,
  536. 0, 0};
  537. g_FlashWindowEx(&FlashInfo);
  538. }
  539. }
  540. else
  541. {
  542. PSTR Message;
  543. if (!g_SessionActive ||
  544. g_ExecStatus == DEBUG_STATUS_NO_DEBUGGEE)
  545. {
  546. Message = "Debuggee not connected";
  547. }
  548. else
  549. {
  550. Message = "Debuggee is running...";
  551. }
  552. SetWindowText(m_hwndEdit, Message);
  553. SendMessage(m_hwndEdit, EM_SETBKGNDCOLOR, 0,
  554. GetSysColor(COLOR_3DFACE));
  555. SendMessage(m_hwndEdit, EM_SETREADONLY, TRUE, 0);
  556. }
  557. if (Type == UPDATE_INPUT_REQUIRED)
  558. {
  559. // Indicate this is an input string and not debugger
  560. // commands.
  561. Prompt = "Input>";
  562. }
  563. else
  564. {
  565. // Put the existing prompt back.
  566. Prompt = g_PromptText;
  567. }
  568. }
  569. else if (Type == UPDATE_PROMPT_TEXT)
  570. {
  571. Prompt = g_PromptText;
  572. }
  573. if (Prompt != NULL)
  574. {
  575. if (Prompt[0] != 0)
  576. {
  577. ULONG Width = (strlen(Prompt) + 1) *
  578. m_Font->Metrics.tmAveCharWidth;
  579. if (Width != m_PromptWidth)
  580. {
  581. m_PromptWidth = Width;
  582. OnSize();
  583. }
  584. }
  585. SetWindowText(m_Prompt, Prompt);
  586. }
  587. }
  588. ULONG
  589. CMDWIN_DATA::GetWorkspaceSize(void)
  590. {
  591. return COMMONWIN_DATA::GetWorkspaceSize() + sizeof(int);
  592. }
  593. PUCHAR
  594. CMDWIN_DATA::SetWorkspace(PUCHAR Data)
  595. {
  596. Data = COMMONWIN_DATA::SetWorkspace(Data);
  597. // The divider position is relative to the top of
  598. // the window. This means that if the window
  599. // grows suddenly the edit area will grow to
  600. // fill the space rather than keeping it the same
  601. // size. To avoid this we save the position
  602. // relative to the bottom of the window so that
  603. // the edit window stays the same size even when
  604. // the overall window changes size.
  605. // Previous versions didn't do this inversion, so
  606. // we mark the new style with a negative value.
  607. *(int*)Data = -(int)(m_Size.cy - m_nDividerPosition);
  608. Data += sizeof(int);
  609. return Data;
  610. }
  611. PUCHAR
  612. CMDWIN_DATA::ApplyWorkspace1(PUCHAR Data, PUCHAR End)
  613. {
  614. Data = COMMONWIN_DATA::ApplyWorkspace1(Data, End);
  615. if (End - Data >= sizeof(int))
  616. {
  617. int Pos = *(int*)Data;
  618. // Handle old-style top-relative positive values and
  619. // new-style bottom-relative negative values.
  620. MoveDivider(Pos >= 0 ? Pos : m_Size.cy + Pos);
  621. Data += sizeof(int);
  622. }
  623. return Data;
  624. }
  625. void
  626. CMDWIN_DATA::MoveDivider(int Pos)
  627. {
  628. if (Pos == m_nDividerPosition)
  629. {
  630. return;
  631. }
  632. m_nDividerPosition = Pos;
  633. m_EditHeight = m_Size.cy - m_nDividerPosition;
  634. if (g_Workspace != NULL)
  635. {
  636. g_Workspace->AddDirty(WSPF_DIRTY_WINDOWS);
  637. }
  638. SendMessage(m_Win, WM_SIZE, SIZE_RESTORED,
  639. MAKELPARAM(m_Size.cx, m_Size.cy));
  640. }
  641. void
  642. CMDWIN_DATA::AddCmdToHistory(PCSTR pszCmd)
  643. /*++
  644. Description:
  645. Add a command to the command history.
  646. If the command already exists, just move it to
  647. the beginning of the list. This way we don't
  648. repeat commands.
  649. --*/
  650. {
  651. Assert(pszCmd);
  652. HISTORY_LIST *p = NULL;
  653. BOOL fWhiteSpace;
  654. BOOL fFoundDuplicate;
  655. //
  656. // Does the command contain whitespace? If it does, it may be a command
  657. // that requires arguments. If it does have arguments, then string
  658. // comparisons must be case sensitive.
  659. //
  660. fWhiteSpace = _tcscspn(pszCmd, _T(" \t") ) != _tcslen(pszCmd);
  661. p = (HISTORY_LIST *) m_listHistory.Flink;
  662. while (p != &m_listHistory)
  663. {
  664. fFoundDuplicate = FALSE;
  665. if (fWhiteSpace)
  666. {
  667. if ( !_tcscmp(p->m_psz, pszCmd) )
  668. {
  669. fFoundDuplicate = TRUE;
  670. }
  671. }
  672. else
  673. {
  674. if ( !_tcsicmp(p->m_psz, pszCmd) )
  675. {
  676. fFoundDuplicate = TRUE;
  677. }
  678. }
  679. if (fFoundDuplicate)
  680. {
  681. RemoveEntryList( (PLIST_ENTRY) p );
  682. InsertHeadList( (PLIST_ENTRY) &m_listHistory, (PLIST_ENTRY) p);
  683. return;
  684. }
  685. p = (HISTORY_LIST *) p->Flink;
  686. }
  687. // This cmd is new to the list, add it
  688. p = new HISTORY_LIST;
  689. if (p != NULL)
  690. {
  691. p->m_psz = _tcsdup(pszCmd);
  692. if (p->m_psz != NULL)
  693. {
  694. InsertHeadList( (PLIST_ENTRY) &m_listHistory, (PLIST_ENTRY) p);
  695. }
  696. else
  697. {
  698. delete p;
  699. }
  700. }
  701. }
  702. void
  703. CMDWIN_DATA::AddText(PTSTR Text, COLORREF Fg, COLORREF Bg)
  704. {
  705. CHARRANGE OrigSel;
  706. POINT OrigScroll;
  707. CHARRANGE TextRange;
  708. #if 0
  709. DebugPrint("Add %d chars, %p - %p\n",
  710. strlen(Text), Text, Text + strlen(Text));
  711. #endif
  712. SendMessage(m_hwndHistory, WM_SETREDRAW, FALSE, 0);
  713. SendMessage(m_hwndHistory, EM_EXGETSEL, 0, (LPARAM)&OrigSel);
  714. SendMessage(m_hwndHistory, EM_GETSCROLLPOS, 0, (LPARAM)&OrigScroll);
  715. // The selection is lost when adding text so discard
  716. // any previous find results.
  717. if (g_AutoCmdScroll && m_FindSel.cpMax >= m_FindSel.cpMin)
  718. {
  719. m_FindSel.cpMin = 1;
  720. m_FindSel.cpMax = 0;
  721. }
  722. //
  723. // are there too many lines in the buffer?
  724. //
  725. INT Overflow;
  726. Overflow = (INT)SendMessage(m_hwndHistory, EM_GETLINECOUNT, 0, 0) -
  727. MAX_CMDWIN_LINES;
  728. if (Overflow > 0)
  729. {
  730. //
  731. // delete more than we need to so it doesn't happen
  732. // every time a line is printed.
  733. //
  734. TextRange.cpMin = 0;
  735. // Get the character index of the 50th line past the overflow.
  736. TextRange.cpMax = (LONG)
  737. SendMessage(m_hwndHistory,
  738. EM_LINEINDEX,
  739. Overflow + 50,
  740. 0
  741. );
  742. SendMessage(m_hwndHistory,
  743. EM_EXSETSEL,
  744. 0,
  745. (LPARAM) &TextRange
  746. );
  747. SendMessage(m_hwndHistory,
  748. EM_REPLACESEL,
  749. FALSE,
  750. (LPARAM) _T("")
  751. );
  752. m_OutputIndex -= TextRange.cpMax;
  753. if (!g_AutoCmdScroll)
  754. {
  755. if (m_FindSel.cpMax >= m_FindSel.cpMin)
  756. {
  757. m_FindSel.cpMin -= TextRange.cpMax;
  758. m_FindSel.cpMax -= TextRange.cpMax;
  759. if (m_FindSel.cpMin < 0)
  760. {
  761. // Find is at least partially gone so
  762. // throw it away.
  763. m_FindSel.cpMin = 1;
  764. m_FindSel.cpMax = 0;
  765. }
  766. }
  767. OrigSel.cpMin -= TextRange.cpMax;
  768. OrigSel.cpMax -= TextRange.cpMax;
  769. if (OrigSel.cpMin < 0)
  770. {
  771. OrigSel.cpMin = 0;
  772. }
  773. if (OrigSel.cpMax < 0)
  774. {
  775. OrigSel.cpMax = 0;
  776. }
  777. }
  778. }
  779. //
  780. // Output the text to the cmd window. The command
  781. // window is emulating a console window so we need
  782. // to emulate the effects of backspaces and carriage returns.
  783. //
  784. for (;;)
  785. {
  786. PSTR Stop, Scan;
  787. char Save;
  788. // Find the first occurrence of an emulated char.
  789. // If the output index is at the end of the text
  790. // there's no need to specially emulate newline.
  791. // This is a very common case and not splitting
  792. // up output for it greatly enhances append performance.
  793. Stop = strchr(Text, '\r');
  794. Scan = strchr(Text, '\b');
  795. if (Stop == NULL || (Scan != NULL && Scan < Stop))
  796. {
  797. Stop = Scan;
  798. }
  799. if (!m_OutputIndexAtEnd)
  800. {
  801. Scan = strchr(Text, '\n');
  802. if (Stop == NULL || (Scan != NULL && Scan < Stop))
  803. {
  804. Stop = Scan;
  805. }
  806. }
  807. // Add all text up to the emulated char.
  808. if (Stop != NULL)
  809. {
  810. Save = *Stop;
  811. *Stop = 0;
  812. }
  813. if (*Text)
  814. {
  815. LONG Len = strlen(Text);
  816. // Replace any text that might already be there.
  817. TextRange.cpMin = m_OutputIndex;
  818. TextRange.cpMax = m_OutputIndex + Len;
  819. SendMessage(m_hwndHistory, EM_EXSETSEL,
  820. 0, (LPARAM)&TextRange);
  821. SendMessage(m_hwndHistory, EM_REPLACESEL,
  822. FALSE, (LPARAM)Text);
  823. m_OutputIndex = TextRange.cpMax;
  824. CHARFORMAT2 Fmt;
  825. ZeroMemory(&Fmt, sizeof(Fmt));
  826. Fmt.cbSize = sizeof(Fmt);
  827. Fmt.dwMask = CFM_COLOR | CFM_BACKCOLOR;
  828. Fmt.crTextColor = Fg;
  829. Fmt.crBackColor = Bg;
  830. SendMessage(m_hwndHistory, EM_EXSETSEL,
  831. 0, (LPARAM)&TextRange);
  832. SendMessage(m_hwndHistory, EM_SETCHARFORMAT,
  833. SCF_SELECTION, (LPARAM)&Fmt);
  834. TextRange.cpMin = TextRange.cpMax;
  835. SendMessage(m_hwndHistory, EM_EXSETSEL,
  836. 0, (LPARAM)&TextRange);
  837. }
  838. // If there weren't any emulated chars all the remaining text
  839. // was just added so we're done.
  840. if (Stop == NULL)
  841. {
  842. break;
  843. }
  844. Text = Stop;
  845. *Stop = Save;
  846. // Emulate the character.
  847. if (Save == '\b')
  848. {
  849. TextRange.cpMax = m_OutputIndex;
  850. do
  851. {
  852. if (m_OutputIndex > 0)
  853. {
  854. m_OutputIndex--;
  855. }
  856. } while (*(++Text) == '\b');
  857. TextRange.cpMin = m_OutputIndex;
  858. SendMessage(m_hwndHistory, EM_EXSETSEL,
  859. 0, (LPARAM)&TextRange);
  860. SendMessage(m_hwndHistory, EM_REPLACESEL,
  861. FALSE, (LPARAM)"");
  862. }
  863. else if (Save == '\n')
  864. {
  865. // Move the output position to the next line.
  866. // This routine always appends text to the very
  867. // end of the control so that's always the last
  868. // position in the control.
  869. TextRange.cpMin = INT_MAX;
  870. TextRange.cpMax = INT_MAX;
  871. SendMessage(m_hwndHistory, EM_EXSETSEL,
  872. 0, (LPARAM)&TextRange);
  873. do
  874. {
  875. SendMessage(m_hwndHistory, EM_REPLACESEL,
  876. FALSE, (LPARAM)"\n");
  877. } while (*(++Text) == '\n');
  878. SendMessage(m_hwndHistory, EM_EXGETSEL,
  879. 0, (LPARAM)&TextRange);
  880. m_OutputIndex = TextRange.cpMax;
  881. m_OutputIndexAtEnd = TRUE;
  882. }
  883. else if (Save == '\r')
  884. {
  885. // Return the output position to the beginning of
  886. // the current line.
  887. TextRange.cpMin = m_OutputIndex;
  888. TextRange.cpMax = m_OutputIndex;
  889. SendMessage(m_hwndHistory, EM_EXSETSEL,
  890. 0, (LPARAM)&TextRange);
  891. m_OutputIndex = (LONG)
  892. SendMessage(m_hwndHistory, EM_LINEINDEX, -1, 0);
  893. m_OutputIndexAtEnd = FALSE;
  894. while (*(++Text) == '\r')
  895. {
  896. // Advance.
  897. }
  898. }
  899. else
  900. {
  901. Assert(FALSE);
  902. }
  903. }
  904. if (g_AutoCmdScroll)
  905. {
  906. // Force the window to scroll to the bottom of the text.
  907. SendMessage(m_hwndHistory, EM_SCROLLCARET, 0, 0);
  908. }
  909. else
  910. {
  911. // Restore original selection.
  912. SendMessage(m_hwndHistory, EM_EXSETSEL, 0, (LPARAM)&OrigSel);
  913. SendMessage(m_hwndHistory, EM_SETSCROLLPOS, 0, (LPARAM)&OrigScroll);
  914. }
  915. SendMessage(m_hwndHistory, WM_SETREDRAW, TRUE, 0);
  916. InvalidateRect(m_hwndHistory, NULL, TRUE);
  917. }
  918. void
  919. CMDWIN_DATA::Clear(void)
  920. {
  921. SetWindowText(m_hwndHistory, "");
  922. m_OutputIndex = 0;
  923. m_OutputIndexAtEnd = TRUE;
  924. }
  925. void
  926. ClearCmdWindow(void)
  927. {
  928. HWND CmdWin = GetCmdHwnd();
  929. if (CmdWin == NULL)
  930. {
  931. return;
  932. }
  933. PCMDWIN_DATA CmdWinData = GetCmdWinData(CmdWin);
  934. if (CmdWinData == NULL)
  935. {
  936. return;
  937. }
  938. CmdWinData->Clear();
  939. }
  940. BOOL
  941. CmdOutput(PTSTR pszStr, COLORREF Fg, COLORREF Bg)
  942. {
  943. PCMDWIN_DATA pCmdWinData;
  944. BOOL fRet = TRUE;
  945. //
  946. // Ensure that the command window exists
  947. //
  948. if ( !GetCmdHwnd() )
  949. {
  950. if ( !NewCmd_CreateWindow(g_hwndMDIClient) )
  951. {
  952. return FALSE;
  953. }
  954. }
  955. pCmdWinData = GetCmdWinData(GetCmdHwnd());
  956. if (!pCmdWinData)
  957. {
  958. return FALSE;
  959. }
  960. pCmdWinData->AddText(pszStr, Fg, Bg);
  961. return TRUE;
  962. }
  963. int
  964. CDECL
  965. CmdLogVar(
  966. WORD wFormat,
  967. ...
  968. )
  969. {
  970. TCHAR szFormat[MAX_MSG_TXT];
  971. TCHAR szText[MAX_VAR_MSG_TXT];
  972. va_list vargs;
  973. // load format string from resource file
  974. Dbg(LoadString(g_hInst, wFormat, (PTSTR)szFormat, MAX_MSG_TXT));
  975. va_start(vargs, wFormat);
  976. _vstprintf(szText, szFormat, vargs);
  977. va_end(vargs);
  978. COLORREF Fg, Bg;
  979. GetOutMaskColors(DEBUG_OUTPUT_NORMAL, &Fg, &Bg);
  980. return CmdOutput(szText, Fg, Bg);
  981. }
  982. void
  983. CmdLogFmt(
  984. PCTSTR lpFmt,
  985. ...
  986. )
  987. {
  988. TCHAR szText[MAX_VAR_MSG_TXT];
  989. va_list vargs;
  990. va_start(vargs, lpFmt);
  991. _vsntprintf(szText, MAX_VAR_MSG_TXT-1, lpFmt, vargs);
  992. va_end(vargs);
  993. COLORREF Fg, Bg;
  994. GetOutMaskColors(DEBUG_OUTPUT_NORMAL, &Fg, &Bg);
  995. CmdOutput(szText, Fg, Bg);
  996. }
  997. void
  998. CmdOpenSourceFile(PSTR File)
  999. {
  1000. char Found[MAX_SOURCE_PATH];
  1001. if (File == NULL)
  1002. {
  1003. CmdLogFmt("Usage: .open filename\n");
  1004. return;
  1005. }
  1006. // Look up the reported file along the source path.
  1007. // XXX drewb - Use first-match and then element walk to
  1008. // determine ambiguities and display resolution UI.
  1009. if (g_pUiLocSymbols->
  1010. FindSourceFile(0, File,
  1011. DEBUG_FIND_SOURCE_BEST_MATCH |
  1012. DEBUG_FIND_SOURCE_FULL_PATH,
  1013. NULL, Found, sizeof(Found), NULL) != S_OK)
  1014. {
  1015. CmdLogFmt("Unable to find '%s'\n", File);
  1016. }
  1017. else
  1018. {
  1019. OpenOrActivateFile(Found, NULL, -1, TRUE, TRUE);
  1020. }
  1021. }
  1022. BOOL
  1023. DirectCommand(PSTR Command)
  1024. {
  1025. char Term;
  1026. PSTR Scan, Arg;
  1027. //
  1028. // Check and see if this is a UI command
  1029. // vs. a command that should go to the engine.
  1030. //
  1031. while (isspace(*Command))
  1032. {
  1033. Command++;
  1034. }
  1035. Scan = Command;
  1036. while (*Scan && !isspace(*Scan))
  1037. {
  1038. Scan++;
  1039. }
  1040. Term = *Scan;
  1041. *Scan = 0;
  1042. // Advance to next nonspace char for arguments.
  1043. if (Term != 0)
  1044. {
  1045. Arg = Scan + 1;
  1046. while (isspace(*Arg))
  1047. {
  1048. Arg++;
  1049. }
  1050. if (*Arg == 0)
  1051. {
  1052. Arg = NULL;
  1053. }
  1054. }
  1055. else
  1056. {
  1057. Arg = NULL;
  1058. }
  1059. if (!_strcmpi(Command, ".cls"))
  1060. {
  1061. ClearCmdWindow();
  1062. }
  1063. else if (!_strcmpi(Command, ".hh"))
  1064. {
  1065. if (Arg == NULL)
  1066. {
  1067. OpenHelpTopic(HELP_TOPIC_TABLE_OF_CONTENTS);
  1068. }
  1069. else
  1070. {
  1071. OpenHelpIndex(Arg);
  1072. }
  1073. }
  1074. else if (!_strcmpi(Command, ".lsrcpath") ||
  1075. !_strcmpi(Command, ".lsrcpath+"))
  1076. {
  1077. *Scan = Term;
  1078. // Apply source path changes to the local symbol
  1079. // object to update its source path.
  1080. if (g_RemoteClient)
  1081. {
  1082. char Path[MAX_ENGINE_PATH];
  1083. // Convert .lsrcpath to .srcpath.
  1084. Command[1] = '.';
  1085. g_pUiLocControl->Execute(DEBUG_OUTCTL_IGNORE, Command + 1,
  1086. DEBUG_EXECUTE_NOT_LOGGED |
  1087. DEBUG_EXECUTE_NO_REPEAT);
  1088. if (g_pUiLocSymbols->GetSourcePath(Path, sizeof(Path),
  1089. NULL) == S_OK)
  1090. {
  1091. CmdLogFmt("Local source search path is: %s\n", Path);
  1092. if (g_Workspace != NULL)
  1093. {
  1094. g_Workspace->SetString(WSP_GLOBAL_LOCAL_SOURCE_PATH, Path);
  1095. }
  1096. }
  1097. // Refresh windows affected by the source path.
  1098. InvalidateStateBuffers(1 << EVENT_BIT);
  1099. UpdateEngine();
  1100. }
  1101. else
  1102. {
  1103. CmdLogFmt("lsrcpath is only enabled for remote clients\n");
  1104. }
  1105. }
  1106. else if (!_strcmpi(Command, ".open"))
  1107. {
  1108. CmdOpenSourceFile(Arg);
  1109. }
  1110. else if (!_strcmpi(Command, ".restart"))
  1111. {
  1112. AddEnumCommand(UIC_RESTART);
  1113. }
  1114. else if (!_strcmpi(Command, ".server"))
  1115. {
  1116. // We don't interpret this but we need to update
  1117. // the title.
  1118. SetTitleServerText("Server '%s'", Arg);
  1119. *Scan = Term;
  1120. return FALSE;
  1121. }
  1122. else
  1123. {
  1124. *Scan = Term;
  1125. return FALSE;
  1126. }
  1127. // Handled so no need to patch up the command.
  1128. return TRUE;
  1129. }
  1130. int
  1131. CmdExecuteCmd(
  1132. PCTSTR pszCmd,
  1133. UiCommand UiCmd
  1134. )
  1135. {
  1136. PCMDWIN_DATA pCmdWinData = NULL;
  1137. PTSTR pszDupe = NULL;
  1138. PTSTR pszToken = NULL;
  1139. if ( !GetCmdHwnd() )
  1140. {
  1141. NewCmd_CreateWindow(g_hwndMDIClient);
  1142. }
  1143. pCmdWinData = GetCmdWinData(GetCmdHwnd());
  1144. pszDupe = _tcsdup(pszCmd);
  1145. pszToken = _tcstok(pszDupe, _T("\r\n") );
  1146. if (pszToken == NULL)
  1147. {
  1148. // Blank command, important for repeats in
  1149. // the engine but not for the history window.
  1150. AddStringCommand(UiCmd, pszCmd);
  1151. }
  1152. else
  1153. {
  1154. for (; pszToken; pszToken = _tcstok(NULL, _T("\r\n") ) )
  1155. {
  1156. if (pCmdWinData)
  1157. {
  1158. pCmdWinData->AddCmdToHistory(pszToken);
  1159. }
  1160. if (!DirectCommand(pszToken))
  1161. {
  1162. AddStringCommand(UiCmd, pszToken);
  1163. }
  1164. }
  1165. }
  1166. free(pszDupe);
  1167. return 0;
  1168. }