Leaked source code of windows server 2003
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.

1524 lines
41 KiB

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