unit _frmMain; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, dxSkinsCore, dxSkinOffice2019Colorful, Vcl.StdCtrls, cxClasses, cxLookAndFeels, dxSkinsForm, Vcl.ExtCtrls, dxGDIPlusClasses, cxGraphics, cxControls, cxLookAndFeelPainters, dxSkinsdxStatusBarPainter, dxStatusBar, IdIOHandler, IdIOHandlerSocket, IdIOHandlerStack, IdSSL, IdSSLOpenSSL, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, IdHTTP, _JSONConfig, dxSkinsdxBarPainter, dxBar, cxContainer, cxEdit, cxTextEdit, cxMaskEdit, cxDropDownEdit, Vcl.Menus, cxButtons, dxSkinscxPCPainter, dxBarBuiltInMenu, cxPC, System.ImageList, Vcl.ImgList, cxCheckBox, cxImageList, SynEdit, SynHighlighterJSON, SynEditHighlighter, SynHighlighterIni, SynHighlighterXML, SynHighlighterHtml, SynEditRegexSearch, SynEditMiscClasses, SynEditSearch, _SearchTextHightlighterSynEditPlugin, SynEditTypes, cxSplitter; type TMethodType = (mtGET, mtPOST, mtPATCH, mtPUT, mtDELETE); type TfrmMain = class(TForm) OpenDialog: TOpenDialog; SaveDialog: TSaveDialog; Panel1: TPanel; dxSkinController: TdxSkinController; Label1: TLabel; Image: TImage; SSLIOHandlerSocketOpenSSL: TIdSSLIOHandlerSocketOpenSSL; dxStatusBarResponse: TdxStatusBar; dxBarManager: TdxBarManager; dxBarManagerBar1: TdxBar; dxBarSubItem3: TdxBarSubItem; dxBarButton3: TdxBarButton; btnSave: TdxBarButton; btnSaveAs: TdxBarButton; dxBarButton6: TdxBarButton; dxBarSubItem1: TdxBarSubItem; dxBarButton7: TdxBarButton; dxBarSubItem2: TdxBarSubItem; tbbtnRun: TdxBarButton; Label2: TLabel; edtFullURL: TcxTextEdit; btnRun: TcxButton; PageControlResponse: TcxPageControl; tabContent: TcxTabSheet; tabHeaders: TcxTabSheet; pnlHeader: TPanel; dxBarSubItem4: TdxBarSubItem; tbbtnStayOnTop: TdxBarButton; edtBaseURL: TcxTextEdit; Label3: TLabel; btnAdd: TcxButton; cxImageList: TcxImageList; btnDelete: TcxButton; comboPresets: TcxComboBox; Label4: TLabel; btnEdit: TcxButton; btnCopyPreset: TcxButton; Label5: TLabel; Label6: TLabel; comboMethod: TcxComboBox; PageControlParams: TcxPageControl; tabURLParams: TcxTabSheet; cxTabSheet2: TcxTabSheet; Panel3: TPanel; Panel4: TPanel; Label7: TLabel; tabPostData: TcxTabSheet; tbbtnAutoOpenLastUsed: TdxBarButton; Label8: TLabel; Panel5: TPanel; comboPostContentType: TcxComboBox; cxTabSheet3: TcxTabSheet; chkUseBasicAuth: TcxCheckBox; Label9: TLabel; edtAuthUsername: TcxTextEdit; edtAuthPassword: TcxTextEdit; Label10: TLabel; dxBarButton2: TdxBarButton; tbbtnAutoSaveProject: TdxBarButton; PopupEditor: TdxBarPopupMenu; pmnuCutJsonContent: TdxBarButton; pmnuCopyJsonContent: TdxBarButton; pmnuPasteJsonContent: TdxBarButton; pmnuFormatJsonContent: TdxBarButton; pmnuUndoJsonContent: TdxBarButton; pmnuRedoJsonContent: TdxBarButton; pmnuSelectAll: TdxBarButton; pmnuCompactJson: TdxBarButton; btnCopyFullURL: TcxButton; dxBarDockControl1: TdxBarDockControl; SynIniSyn: TSynIniSyn; SynJSONSyn: TSynJSONSyn; EditorContent: TSynEdit; EditorResponseHeaders: TSynEdit; EditorURLParams: TSynEdit; EditorExtraHeaders: TSynEdit; EditorPostData: TSynEdit; SynXMLSyn: TSynXMLSyn; pmnuFormatXML: TdxBarButton; pmnuCompactXML: TdxBarButton; pmnuBiggerEditor: TdxBarButton; btnPasteFullURL: TcxButton; SynHTMLSyn: TSynHTMLSyn; SynEditSearch: TSynEditSearch; SynEditRegexSearch: TSynEditRegexSearch; cxSplitter1: TcxSplitter; dxStatusBarApp: TdxStatusBar; Panel2: TPanel; chkResponseAutoformat: TcxCheckBox; procedure FormShow(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure dxBarButton6Click(Sender: TObject); procedure dxBarButton3Click(Sender: TObject); procedure btnSaveAsClick(Sender: TObject); procedure dxBarButton7Click(Sender: TObject); procedure tbbtnRunClick(Sender: TObject); procedure dxBarSubItem3Popup(Sender: TObject); procedure btnRunClick(Sender: TObject); procedure FormResize(Sender: TObject); procedure tbbtnStayOnTopClick(Sender: TObject); procedure pmnuCutJsonContentClick(Sender: TObject); procedure pmnuPasteJsonContentClick(Sender: TObject); procedure pmnuCopyJsonContentClick(Sender: TObject); procedure btnAddClick(Sender: TObject); procedure btnDeleteClick(Sender: TObject); procedure comboPresetsPropertiesChange(Sender: TObject); procedure btnSaveClick(Sender: TObject); procedure btnEditClick(Sender: TObject); procedure btnCopyPresetClick(Sender: TObject); procedure edtBaseURLPropertiesChange(Sender: TObject); procedure EditorURLParamsChange(Sender: TObject); procedure comboMethodPropertiesChange(Sender: TObject); procedure tbbtnAutoOpenLastUsedClick(Sender: TObject); procedure dxBarButton2Click(Sender: TObject); procedure tbbtnAutoSaveProjectClick(Sender: TObject); procedure pmnuFormatJsonContentClick(Sender: TObject); procedure EditorContentMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure EditorPostDataMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure PopupEditorPopup(Sender: TObject); procedure pmnuUndoJsonContentClick(Sender: TObject); procedure pmnuRedoJsonContentClick(Sender: TObject); procedure pmnuSelectAllClick(Sender: TObject); procedure pmnuCompactJsonClick(Sender: TObject); procedure EditorResponseHeadersMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure EditorExtraHeadersMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure btnCopyFullURLClick(Sender: TObject); procedure EditorURLParamsMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure pmnuFormatXMLClick(Sender: TObject); procedure pmnuCompactXMLClick(Sender: TObject); procedure comboPostContentTypePropertiesChange(Sender: TObject); procedure pmnuBiggerEditorClick(Sender: TObject); procedure EditorContentKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); procedure edtFullURLPropertiesChange(Sender: TObject); procedure btnPasteFullURLClick(Sender: TObject); procedure EditorResponseHeadersKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); procedure EditorURLParamsKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); procedure EditorExtraHeadersKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); procedure EditorPostDataKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); procedure EditorEnter(Sender: TObject); procedure EditorExit(Sender: TObject); private FProjectConfig: TJSONConfig; FActiveProject: string; FActivePreset: string; FIsTempProjectLoaded: Boolean; FTempProjectFile: string; FRoamingSavePath: string; FSearchFromCaret: Boolean; FPresetsList: TStringList; FHTTP: TIdHTTP; function GetTempProjectFile: string; function GetAppVersion: string; procedure UpdateFullURL; procedure LoadProject; procedure SaveProject; procedure SaveProjectAs(const LoadAfterSave: Boolean); procedure Run; procedure AddPreset(const APresetName: string); procedure DeletePreset(const AID: Integer); procedure LoadPreset; procedure SavePreset; procedure ShowError(const AErrorText: string); procedure DoGETRequest(const AMethodType: TMethodType); procedure DoPOSTRequest(const AMethodType: TMethodType); procedure UpdateEditor(const AText: string); procedure UpdatePresetsCombobox; procedure CheckForUpdate; function GenerateGuid: string; function Encode(const AText: string): string; function Decode(const AText: string): string; public MainConfig: TJSONConfig; ActiveEditor: TSynEdit; gbSearchBackwards: Boolean; gbSearchCaseSensitive: Boolean; gbSearchFromCaret: Boolean; gbSearchSelectionOnly: Boolean; gbSearchTextAtCaret: Boolean; gbSearchWholeWords: Boolean; gbSearchRegex: Boolean; gsSearchText: string; gsSearchTextHistory: string; gsReplaceText: string; gsReplaceTextHistory: string; procedure DoSearchReplaceText(AReplace: Boolean; ABackwards: Boolean); procedure ShowSearchReplaceDialog(AReplace: Boolean); end; const iCurrentConfigVersion: Integer = 2; var frmMain: TfrmMain; implementation {$R *.dfm} uses System.NetEncoding, System.UITypes, _EncryptStr, JsonDataObjects, IdGlobal, System.IOUtils, uRwXmlDOM, _frmBiggerEditor, IdUri, _frmSearch, ShellApi; procedure TfrmMain.tbbtnRunClick(Sender: TObject); begin Run; end; procedure TfrmMain.tbbtnStayOnTopClick(Sender: TObject); begin if tbbtnStayOnTop.Down then begin FormStyle := fsStayOnTop; end else begin FormStyle := fsNormal; end; end; procedure TfrmMain.UpdateEditor(const AText: string); begin ActiveEditor.Lines.Text := AText; end; procedure TfrmMain.UpdateFullURL; var i: Integer; dmy: string; strlist: TStringList; begin edtFullURL.Text := edtBaseURL.Text; if EditorURLParams.Lines.Count > 0 then begin dmy := '?'; for i := 0 to EditorURLParams.Lines.Count - 1 do begin if dmy <> '?' then dmy := dmy + '&'; strlist := TStringList.Create; try strlist.Text := StringReplace(EditorURLParams.Lines[i], '=', #13#10, [rfReplaceAll]); if strlist.Count = 2 then begin dmy := dmy + TNetEncoding.URL.Encode(strlist[0]) + '=' + TNetEncoding.URL.Encode(strlist[1]); end; finally strlist.Free; end; end; edtFullURL.Text := edtFullURL.Text + dmy; end; end; procedure TfrmMain.UpdatePresetsCombobox; var i: Integer; begin comboPresets.Properties.Items.Clear; for i := 0 to FPresetsList.Count - 1 do begin comboPresets.Properties.Items.Add(Decode(FPresetsList.ValueFromIndex[i])); end; end; procedure TfrmMain.tbbtnAutoOpenLastUsedClick(Sender: TObject); begin MainConfig.WriteBool('Project', 'AutoOpenLastUsed', tbbtnAutoOpenLastUsed.Down); end; procedure TfrmMain.tbbtnAutoSaveProjectClick(Sender: TObject); begin MainConfig.WriteBool('Project', 'AutoSaveProject', tbbtnAutoSaveProject.Down); end; procedure TfrmMain.dxBarButton2Click(Sender: TObject); begin FActiveProject := GetTempProjectFile; FIsTempProjectLoaded := True; LoadProject; end; procedure TfrmMain.dxBarButton3Click(Sender: TObject); begin OpenDialog.InitialDir := MainConfig.ReadString('Dialogs', 'LastOpenDir', ExtractFilePath(ParamStr(0))); if OpenDialog.Execute then begin MainConfig.WriteString('Dialogs', 'LastOpenDir', ExtractFilePath(OpenDialog.FileName)); FActiveProject := OpenDialog.FileName; FIsTempProjectLoaded := False; LoadProject; end; end; procedure TfrmMain.AddPreset(const APresetName: string); begin FPresetsList.AddPair(GenerateGuid, Encode(APresetName)); UpdatePresetsCombobox; comboPresets.ItemIndex := comboPresets.Properties.Items.Count - 1; FProjectConfig.WriteString('Presets', 'Presets', FPresetsList.Text); end; procedure TfrmMain.btnAddClick(Sender: TObject); var dmy: string; begin if InputQuery('Add Preset', 'Enter a preset name:', dmy) then begin AddPreset(dmy); end; end; procedure TfrmMain.btnDeleteClick(Sender: TObject); begin if comboPresets.Properties.Items.Count = 1 then begin MessageDlg('Thet last preset can''t be deleted. Please create a new preset if you want to delete this.', mtError, [mbOK], 0); end else begin if MessageDlg('Really delete preset "' + comboPresets.Text + '"?', mtConfirmation, [mbYes, mbNo], 0) = mrYes then begin DeletePreset(comboPresets.ItemIndex); FProjectConfig.WriteString('Presets', 'Presets', FPresetsList.Text); end; end; end; procedure TfrmMain.btnEditClick(Sender: TObject); var dmy: string; idx: Integer; begin SavePreset; dmy := comboPresets.Text; idx := comboPresets.ItemIndex; if InputQuery('Edit Preset', 'Rename preset:', dmy) then begin comboPresets.Properties.Items[comboPresets.ItemIndex] := dmy; FPresetsList.ValueFromIndex[idx] := Encode(dmy); comboPresets.ItemIndex := idx; FProjectConfig.WriteString('Presets', 'Presets', FPresetsList.Text); end; end; procedure TfrmMain.btnPasteFullURLClick(Sender: TObject); begin edtFullURL.Clear; edtFullURL.SetFocus; edtFullURL.PasteFromClipboard; end; procedure TfrmMain.btnCopyFullURLClick(Sender: TObject); begin edtFullURL.SelectAll; edtFullURL.CopyToClipboard; end; procedure TfrmMain.btnCopyPresetClick(Sender: TObject); var idx: Integer; begin SavePreset; idx := comboPresets.ItemIndex; AddPreset('Copy of ' + comboPresets.Text); FProjectConfig.CopySection('Preset_' + FPresetsList.Names[idx], 'Preset_' + FPresetsList.Names[comboPresets.ItemIndex]); LoadPreset; end; procedure TfrmMain.btnRunClick(Sender: TObject); begin Run; end; procedure TfrmMain.btnSaveAsClick(Sender: TObject); begin SaveProjectAs(True); end; procedure TfrmMain.btnSaveClick(Sender: TObject); begin SaveProject; end; procedure TfrmMain.CheckForUpdate; begin TThread.CreateAnonymousThread( procedure var HTTP: TIdHTTP; bNewVersion: Boolean; strlist: TStringList; fs: TFileStream; UpdateFile: TStringList; begin bNewVersion := False; HTTP := TIdHTTP.Create(nil); strlist := TStringList.Create; try try strlist.Text := HTTP.Get('https://www.simnet.cx/WebUpdate/RESTDebugger/Update.txt'); except bNewVersion := False; raise; end; if strlist.Count = 2 then begin if strlist[0] = GetAppVersion then begin bNewVersion := False; end else begin DeleteFile(IncludeTrailingPathDelimiter(TPath.GetTempPath) + 'Update.exe'); fs := TFileStream.Create(IncludeTrailingPathDelimiter(TPath.GetTempPath) + 'Update.exe', fmCreate); try try HTTP.Get(strlist[1], fs); except bNewVersion := False; raise; end; finally fs.Free; end; bNewVersion := True; end; end; finally strlist.Free; HTTP.Free; TThread.Synchronize(TThread.CurrentThread, procedure begin if bNewVersion then begin //Create Update file and start it UpdateFile := TStringList.Create; try UpdateFile.Add('timeout 1'); UpdateFile.Add('taskkill /F /IM RESTDebugger.exe'); UpdateFile.Add('"%~dp0Update.exe" -o"' + ExtractFilePath(ParamStr(0)) + '" -y'); UpdateFile.Add('start "" "' + ParamStr(0) + '"'); UpdateFile.Add('del "%~dp0Update.exe"'); UpdateFile.Add('del "%~dp0Update.cmd"'); UpdateFile.SaveToFile(IncludeTrailingPathDelimiter(TPath.GetTempPath) + 'Update.cmd'); finally UpdateFile.Free; end; if MessageDlg('Es ist eine neue Software Version verfügbar. Möchten Sie diese nun installieren?', mtConfirmation, [mbYes, mbNo], 0) = mrYes then begin ShellExecute(handle, 'open', PChar(IncludeTrailingPathDelimiter(TPath.GetTempPath) + 'Update.cmd'), nil, nil, SW_HIDE); Close; end else begin //CleanUp temp files DeleteFile(IncludeTrailingPathDelimiter(TPath.GetTempPath) + 'Update.cmd'); DeleteFile(IncludeTrailingPathDelimiter(TPath.GetTempPath) + 'Update.exe'); end; end; end); end; end).Start; end; procedure TfrmMain.comboMethodPropertiesChange(Sender: TObject); begin tabPostData.TabVisible := (comboMethod.ItemIndex = 1) or (comboMethod.ItemIndex = 2) or (comboMethod.ItemIndex = 3); case comboMethod.ItemIndex of 1: tabPostData.Caption := 'Post Data'; 2: tabPostData.Caption := 'Patch Data'; 3: tabPostData.Caption := 'Put Data'; end; if (comboMethod.ItemIndex = 0) and (PageControlParams.ActivePage = nil) then begin PageControlParams.ActivePage := tabURLParams; end; end; procedure TfrmMain.comboPostContentTypePropertiesChange(Sender: TObject); begin if Pos('application/json', comboPostContentType.Text) > 0 then begin EditorPostData.Highlighter := SynJSONSyn; end else if Pos('text/xml', comboPostContentType.Text) > 0 then begin EditorPostData.Highlighter := SynXMLSyn; end else if Pos('text/html', comboPostContentType.Text) > 0 then begin EditorPostData.Highlighter := SynHTMLSyn; end else begin EditorPostData.Highlighter := nil; end; end; procedure TfrmMain.comboPresetsPropertiesChange(Sender: TObject); begin if FActivePreset <> '' then begin SavePreset; end; FActivePreset := comboPresets.Text; LoadPreset; end; procedure TfrmMain.pmnuBiggerEditorClick(Sender: TObject); begin frmBiggerEditor.ShowModal; UpdateFullURL; end; procedure TfrmMain.pmnuCompactJsonClick(Sender: TObject); var ja: TJsonArray; jo: TJsonObject; begin if ActiveEditor.Lines.Count > 0 then begin if Length(ActiveEditor.Lines[0]) > 0 then begin if ActiveEditor.Lines[0][1] = '{' then begin jo := TJsonObject.Create; Screen.Cursor := crHourGlass; try try jo.FromJSON(ActiveEditor.Lines.Text); except on E: Exception do begin ShowError(E.Message); end; end; UpdateEditor(jo.ToJSON(True)); finally Screen.Cursor := crDefault; jo.Free; end; end else if ActiveEditor.Lines[0][1] = '[' then begin ja := TJsonArray.Create; Screen.Cursor := crHourGlass; try try ja.FromJSON(ActiveEditor.Lines.Text); except on E: Exception do begin ShowError(E.Message); end; end; UpdateEditor(ja.ToJSON(True)); finally Screen.Cursor := crDefault; ja.Free; end; end; end; end; end; procedure TfrmMain.pmnuCompactXMLClick(Sender: TObject); var xml: TRwDomDocument; begin xml := TRwDOMDocument.Create; Screen.Cursor := crHourGlass; try xml.LoadXML(ActiveEditor.Lines.Text); UpdateEditor(xml.XML); finally xml.Free; Screen.Cursor := crDefault; end; end; procedure TfrmMain.pmnuCopyJsonContentClick(Sender: TObject); begin ActiveEditor.CopyToClipboard; end; procedure TfrmMain.pmnuCutJsonContentClick(Sender: TObject); begin ActiveEditor.CutToClipboard; end; procedure TfrmMain.DeletePreset(const AID: Integer); begin comboPresets.Properties.OnChange := nil; comboPresets.Properties.Items.Delete(AID); FProjectConfig.DeleteSection('Preset_' + FPresetsList.Names[AID]); FPresetsList.Delete(AID); comboPresets.ItemIndex := comboPresets.Properties.Items.Count - 1; FActivePreset := comboPresets.Text; LoadPreset; comboPresets.Properties.OnChange := comboPresetsPropertiesChange; end; function TfrmMain.Decode(const AText: string): string; begin Result := TNetEncoding.Base64.Decode(AText); end; procedure TfrmMain.DoGETRequest(const AMethodType: TMethodType); var dmy: string; begin try case AMethodType of mtGET: dmy := FHTTP.Get(edtFullURL.Text); mtDELETE: dmy := FHTTP.Delete(edtFullURL.Text); end; if FHTTP.ResponseCode.ToString[1] <> '2' then begin ShowError(FHTTP.ResponseText); end; except on E: EIdHTTPProtocolException do begin dmy := E.ErrorMessage; ShowError(E.Message); end; end; ActiveEditor := EditorContent; UpdateEditor(dmy); ActiveEditor := EditorResponseHeaders; UpdateEditor(FHTTP.Response.RawHeaders.Text); end; procedure TfrmMain.DoPOSTRequest(const AMethodType: TMethodType); var dmy: string; strstream: TStringStream; begin strstream := TStringStream.Create(EditorPostData.Lines.Text, TEncoding.UTF8); try FHTTP.Request.ContentType := comboPostContentType.Text; FHTTP.Request.CharSet := 'utf-8'; try case AMethodType of mtPOST: dmy := FHTTP.Post(edtFullURL.Text, strstream); mtPATCH: dmy := FHTTP.Patch(edtFullURL.Text, strstream); mtPUT: dmy := FHTTP.Put(edtFullURL.Text, strstream); end; if FHTTP.ResponseCode.ToString[1] <> '2' then begin ShowError(FHTTP.ResponseText); end; except on E: EIdHTTPProtocolException do begin dmy := E.ErrorMessage; ShowError(E.Message); end; end; finally strstream.Free; end; ActiveEditor := EditorContent; UpdateEditor(dmy); ActiveEditor := EditorResponseHeaders; UpdateEditor(FHTTP.Response.RawHeaders.Text); end; procedure TfrmMain.DoSearchReplaceText(AReplace, ABackwards: Boolean); var Options: TSynSearchOptions; begin if AReplace then Options := [ssoPrompt, ssoReplace, ssoReplaceAll] else Options := []; if ABackwards then Include(Options, ssoBackwards); if gbSearchCaseSensitive then Include(Options, ssoMatchCase); if not FSearchFromCaret then Include(Options, ssoEntireScope); if gbSearchSelectionOnly then Include(Options, ssoSelectedOnly); if gbSearchWholeWords then Include(Options, ssoWholeWord); if gbSearchRegex then ActiveEditor.SearchEngine := SynEditRegexSearch else ActiveEditor.SearchEngine := SynEditSearch; if ActiveEditor.SearchReplace(gsSearchText, gsReplaceText, Options) = 0 then begin MessageDlg('Text not found.', mtInformation, [mbOK], 0); end; end; procedure TfrmMain.dxBarButton6Click(Sender: TObject); begin Close; end; procedure TfrmMain.dxBarButton7Click(Sender: TObject); begin MessageDlg('Simnet REST Debugger' + #13#10 + GetAppVersion, mtInformation, [mbOK], 0); end; procedure TfrmMain.dxBarSubItem3Popup(Sender: TObject); begin btnSave.Enabled := not FIsTempProjectLoaded; end; procedure TfrmMain.EditorEnter(Sender: TObject); begin (Sender as TSynEdit).ActiveLineColor := $00E6FFFA; end; procedure TfrmMain.EditorExit(Sender: TObject); begin (Sender as TSynEdit).ActiveLineColor := clNone; end; procedure TfrmMain.EditorContentKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); begin if Key = vkF11 then begin ActiveEditor := EditorContent; pmnuBiggerEditorClick(Sender); end; if Key = vkF3 then begin ActiveEditor := EditorContent; DoSearchReplaceText(false, gbSearchBackwards); end; if (Key = 70) and (Shift = [ssCtrl]) then begin ActiveEditor := EditorContent; ShowSearchReplaceDialog(false); end; end; procedure TfrmMain.EditorContentMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin if Button = mbRight then begin ActiveEditor := EditorContent; PopupEditor.PopupFromCursorPos; end; end; procedure TfrmMain.EditorExtraHeadersKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); begin if Key = vkF11 then begin ActiveEditor := EditorExtraHeaders; pmnuBiggerEditorClick(Sender); end; end; procedure TfrmMain.EditorExtraHeadersMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin if Button = mbRight then begin ActiveEditor := EditorExtraHeaders; PopupEditor.PopupFromCursorPos; end; end; procedure TfrmMain.EditorResponseHeadersKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); begin if Key = vkF11 then begin ActiveEditor := EditorResponseHeaders; pmnuBiggerEditorClick(Sender); end; end; procedure TfrmMain.EditorResponseHeadersMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin if Button = mbRight then begin ActiveEditor := EditorResponseHeaders; PopupEditor.PopupFromCursorPos; end; end; procedure TfrmMain.EditorPostDataKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); begin if Key = vkF11 then begin ActiveEditor := EditorPostData; pmnuBiggerEditorClick(Sender); end; end; procedure TfrmMain.EditorPostDataMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin if Button = mbRight then begin ActiveEditor := EditorPostData; PopupEditor.PopupFromCursorPos; end; end; procedure TfrmMain.EditorURLParamsChange(Sender: TObject); begin UpdateFullURL; end; procedure TfrmMain.EditorURLParamsKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); begin if Key = vkF11 then begin ActiveEditor := EditorURLParams; pmnuBiggerEditorClick(Sender); end; end; procedure TfrmMain.EditorURLParamsMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin if Button = mbRight then begin ActiveEditor := EditorURLParams; PopupEditor.PopupFromCursorPos; end; end; procedure TfrmMain.edtBaseURLPropertiesChange(Sender: TObject); begin if edtBaseURL.Focused then begin UpdateFullURL; end; end; procedure TfrmMain.edtFullURLPropertiesChange(Sender: TObject); var uri: TIdURI; begin if edtFullURL.Focused then begin uri := TIdURI.Create(edtFullURL.Text); try EditorURLParams.Lines.Text := StringReplace(uri.Params, '&', #13#10, [rfReplaceAll]); edtBaseURL.Text := StringReplace(edtFullURL.Text, '?' + uri.Params, '', [rfReplaceAll]); finally uri.Free; end; end; end; function TfrmMain.Encode(const AText: string): string; begin Result := StringReplace(TNetEncoding.Base64.Encode(AText), #13#10, '', [rfReplaceAll]); end; procedure TfrmMain.FormClose(Sender: TObject; var Action: TCloseAction); begin MainConfig.WriteInteger('MainWindowPos', 'Left', Left); MainConfig.WriteInteger('MainWindowPos', 'Top', Top); MainConfig.WriteInteger('MainWindowPos', 'Width', Width); MainConfig.WriteInteger('MainWindowPos', 'Height', Height); MainConfig.WriteBool('MainWindowPos', 'StayOnTop', FormStyle = fsStayOnTop); MainConfig.WriteInteger('MainWindowPos', 'HeaderHeight', pnlHeader.Height); MainConfig.WriteBool('Search', 'gbSearchBackwards', gbSearchBackwards); MainConfig.WriteBool('Search', 'gbSearchCaseSensitive', gbSearchCaseSensitive); MainConfig.WriteBool('Search', 'gbSearchFromCaret', gbSearchFromCaret); MainConfig.WriteBool('Search', 'gbSearchSelectionOnly', gbSearchSelectionOnly); MainConfig.WriteBool('Search', 'gbSearchTextAtCaret', gbSearchTextAtCaret); MainConfig.WriteBool('Search', 'gbSearchWholeWords', gbSearchWholeWords); MainConfig.WriteBool('Search', 'gbSearchRegex', gbSearchRegex); MainConfig.WriteString('Search', 'gsSearchText', gsSearchText); MainConfig.WriteString('Search', 'gsSearchTextHistory', gsSearchTextHistory); MainConfig.WriteString('Search', 'gsReplaceText', gsReplaceText); MainConfig.WriteString('Search', 'gsReplaceTextHistory', gsReplaceTextHistory); SavePreset; if FProjectConfig.FileModified then begin if (tbbtnAutoSaveProject.Down) and (not FIsTempProjectLoaded) then begin SaveProject; end else begin if MessageDlg('The project file has changed. Do you want to save it?', mtConfirmation, [mbYes, mbNo], 0) = mrYes then begin if FIsTempProjectLoaded then begin SaveProjectAs(False); end else begin SaveProject; end; end; end; end; if FileExists(FTempProjectFile) then begin DeleteFile(FTempProjectFile); end; end; procedure TfrmMain.FormCreate(Sender: TObject); begin FRoamingSavePath := IncludeTrailingPathDelimiter(TPath.GetHomePath) + 'Simnet\RESTDebugger\'; ForceDirectories(FRoamingSavePath); MainConfig := TJSONConfig.Create(FRoamingSavePath + 'AppConfig.json'); FActiveProject := ''; FActivePreset := ''; FPresetsList := TStringList.Create; FPresetsList.NameValueSeparator := ':'; ActiveEditor := nil; if (not MainConfig.ReadBool('Main', 'FirstStartShown')) and (FileExists(IncludeTrailingPathDelimiter(ExtractFilePath(ParamStr(0))) + 'Example Config.rdproj')) then begin FActiveProject := IncludeTrailingPathDelimiter(ExtractFilePath(ParamStr(0))) + 'Example Config.rdproj'; FIsTempProjectLoaded := False; MainConfig.WriteBool('Main', 'FirstStartShown', True); MainConfig.WriteString('Project', 'LastUsed', FActiveProject); end else begin if (ParamStr(1) <> '') then begin FActiveProject := ParamStr(1); FIsTempProjectLoaded := False; end else begin if (MainConfig.ReadBool('Project', 'AutoOpenLastUsed', True)) and (FileExists(MainConfig.ReadString('Project', 'LastUsed'))) then begin FActiveProject := MainConfig.ReadString('Project', 'LastUsed'); FIsTempProjectLoaded := False; end else begin FActiveProject := GetTempProjectFile; FIsTempProjectLoaded := True; end; end; end; with TSearchTextHightlighterSynEditPlugin.Create(EditorContent) do begin Attribute.Background := $0078AAFF; end; LoadProject; end; procedure TfrmMain.FormDestroy(Sender: TObject); begin FPresetsList.Free; FProjectConfig.Free; MainConfig.Free; end; procedure TfrmMain.FormResize(Sender: TObject); begin Image.Left := Width - 45; end; procedure TfrmMain.FormShow(Sender: TObject); begin dxStatusBarApp.Panels[0].Text := 'Copyright © by Simnet ' + FormatDateTime('yyyy', now); Left := MainConfig.ReadInteger('MainWindowPos', 'Left', Round((Screen.Width - Width) div 2)); if Left >= Screen.Width then begin Left := 10; end; Top := MainConfig.ReadInteger('MainWindowPos', 'Top', Round((Screen.Height - Height) div 2)); if Top >= Screen.Height then begin Top := 10; end; Width := MainConfig.ReadInteger('MainWindowPos', 'Width', 720); Height := MainConfig.ReadInteger('MainWindowPos', 'Height', 930); if MainConfig.ReadBool('MainWindowPos', 'StayOnTop') then begin FormStyle := fsStayOnTop; tbbtnStayOnTop.Down := True; end else begin FormStyle := fsNormal; tbbtnStayOnTop.Down := False; end; pnlHeader.Height := MainConfig.ReadInteger('MainWindowPos', 'HeaderHeight', 370); tbbtnAutoOpenLastUsed.Down := MainConfig.ReadBool('Project', 'AutoOpenLastUsed', True); tbbtnAutoSaveProject.Down := MainConfig.ReadBool('Project', 'AutoSaveProject', True); EditorContent.Lines.Clear; EditorResponseHeaders.Lines.Clear; dxStatusBarResponse.Panels[0].Text := ''; dxStatusBarResponse.Panels[1].Text := ''; dxStatusBarResponse.Panels[2].Text := ''; gbSearchBackwards := MainConfig.ReadBool('Search', 'gbSearchBackwards', gbSearchBackwards); gbSearchCaseSensitive := MainConfig.ReadBool('Search', 'gbSearchCaseSensitive', gbSearchCaseSensitive); gbSearchFromCaret :=MainConfig.ReadBool('Search', 'gbSearchFromCaret', gbSearchFromCaret); gbSearchSelectionOnly := MainConfig.ReadBool('Search', 'gbSearchSelectionOnly', gbSearchSelectionOnly); gbSearchTextAtCaret := MainConfig.ReadBool('Search', 'gbSearchTextAtCaret', gbSearchTextAtCaret); gbSearchWholeWords := MainConfig.ReadBool('Search', 'gbSearchWholeWords', gbSearchWholeWords); gbSearchRegex := MainConfig.ReadBool('Search', 'gbSearchRegex', gbSearchRegex); gsSearchText := MainConfig.ReadString('Search', 'gsSearchText', gsSearchText); gsSearchTextHistory := MainConfig.ReadString('Search', 'gsSearchTextHistory', gsSearchTextHistory); gsReplaceText := MainConfig.ReadString('Search', 'gsReplaceText', gsReplaceText); gsReplaceTextHistory := MainConfig.ReadString('Search', 'gsReplaceTextHistory', gsReplaceTextHistory); CheckForUpdate; end; function TfrmMain.GenerateGuid: string; var guid: TGUID; begin CreateGUID(guid); Result := GUIDToString(guid); Result := StringReplace(Result, '{', '', [rfReplaceAll]); Result := StringReplace(Result, '}', '', [rfReplaceAll]); Result := StringReplace(Result, '-', '', [rfReplaceAll]); Result := LowerCase(Result); end; function TfrmMain.GetAppVersion: string; var VerInfoSize: DWORD; VerInfo: Pointer; VerValueSize: DWORD; VerValue: PVSFixedFileInfo; dmy: DWORD; begin Result := ''; VerInfoSize := GetFileVersionInfoSize(PChar(ParamStr(0)), dmy); if VerInfoSize = 0 then Exit; GetMem(VerInfo, VerInfoSize); try GetFileVersionInfo(PChar(ParamStr(0)), 0, VerInfoSize, VerInfo); VerQueryValue(VerInfo, '\', Pointer(VerValue), VerValueSize); with VerValue^ do begin Result := IntToStr(dwFileVersionMS shr 16); Result := Result + '.' + IntToStr(dwFileVersionMS and $FFFF); Result := Result + '.' + IntToStr(dwFileVersionLS shr 16); Result := Result + '.' + IntToStr(dwFileVersionLS and $FFFF); end; finally FreeMem(VerInfo, VerInfoSize); end; end; function TfrmMain.GetTempProjectFile: string; var iSize: Integer; guid: TGUID; begin iSize := Winapi.Windows.GetTempPath(0, nil); SetLength(Result, iSize); iSize := Winapi.Windows.GetTempPath(iSize, @Result[1]); SetLength(Result, iSize); Result := IncludeTrailingPathDelimiter(Result); CreateGUID(guid); Result := Result + guid.ToString + '.rdproj'; FTempProjectFile := Result; end; procedure TfrmMain.LoadPreset; var preset: string; idx: Integer; begin idx := comboPresets.Properties.Items.IndexOf(FActivePreset); if idx > -1 then begin preset := 'Preset_' + FPresetsList.Names[idx]; edtBaseURL.Text := FProjectConfig.ReadString(preset, 'BaseURL'); comboMethod.ItemIndex := FProjectConfig.ReadInteger(preset, 'Method', 0); EditorURLParams.Lines.Text := FProjectConfig.ReadString(preset, 'URLParams'); EditorExtraHeaders.Lines.Text := FProjectConfig.ReadString(preset, 'ExtraHeaders'); EditorPostData.Lines.Text := FProjectConfig.ReadString(preset, 'PostData'); comboPostContentType.Text := FProjectConfig.ReadString(preset, 'PostDataContentType', 'application/json'); chkResponseAutoformat.Checked := FProjectConfig.ReadBool(preset, 'ResponseAutoFormat', false); chkUseBasicAuth.Checked := FProjectConfig.ReadBool(preset, 'UseBasicAuth'); edtAuthUsername.Text := TEncryptStr.DecryptString(FProjectConfig.ReadString(preset, 'AuthUsername')); edtAuthPassword.Text := TEncryptStr.DecryptString(FProjectConfig.ReadString(preset, 'AuthPassword')); UpdateFullURL; end; end; procedure TfrmMain.LoadProject; var strlist: TStringList; i: Integer; begin if FIsTempProjectLoaded then begin dxStatusBarApp.Panels[1].Text := 'Default project loaded.'; Caption := 'Simnet REST Debugger'; end else begin dxStatusBarApp.Panels[1].Text := 'Loaded project: ' + FActiveProject; Caption := ExtractFileName(FActiveProject) + ' - Simnet REST Debugger'; MainConfig.WriteString('Project', 'LastUsed', FActiveProject); end; if Assigned(FProjectConfig) then begin FProjectConfig.Free; end; FProjectConfig := TJSONConfig.Create(FActiveProject, False); FProjectConfig.AutoSave := False; if FProjectConfig.ReadInteger('Presets', 'ConfigVersion') <> iCurrentConfigVersion then begin //Upgrade config to newest version strlist := TStringList.Create; try strlist.Text := FProjectConfig.ReadString('Presets', 'Presets'); FPresetsList.Clear; for i := 0 to strlist.Count - 1 do begin FPresetsList.AddPair(GenerateGuid, Encode(strlist[i])); FProjectConfig.RenameSection('Preset_' + i.ToString, 'Preset_' + FPresetsList.Names[i]); end; FProjectConfig.WriteString('Presets', 'Presets', FPresetsList.Text); FProjectConfig.WriteInteger('Presets', 'ConfigVersion', iCurrentConfigVersion); FProjectConfig.Save; finally strlist.Free; end; end; FPresetsList.Clear; FPresetsList.DelimitedText := FProjectConfig.ReadString('Presets', 'Presets'); UpdatePresetsCombobox; if comboPresets.Properties.Items.Count = 0 then begin // Add default AddPreset('Default'); FProjectConfig.Save; end else begin comboPresets.ItemIndex := FProjectConfig.ReadInteger('Presets', 'LastUsed', 0); end; PageControlParams.ActivePage := tabURLParams; PageControlResponse.ActivePage := tabContent; end; procedure TfrmMain.pmnuPasteJsonContentClick(Sender: TObject); begin ActiveEditor.PasteFromClipboard; end; procedure TfrmMain.pmnuRedoJsonContentClick(Sender: TObject); begin ActiveEditor.Redo; end; procedure TfrmMain.pmnuSelectAllClick(Sender: TObject); begin ActiveEditor.SelectAll; end; procedure TfrmMain.pmnuUndoJsonContentClick(Sender: TObject); begin ActiveEditor.Undo; end; procedure TfrmMain.PopupEditorPopup(Sender: TObject); begin pmnuPasteJsonContent.Enabled := ActiveEditor.CanPaste; pmnuUndoJsonContent.Enabled := ActiveEditor.CanUndo; pmnuRedoJsonContent.Enabled := ActiveEditor.CanRedo; if ActiveEditor.Highlighter = SynJSONSyn then begin pmnuFormatJsonContent.Visible := ivAlways; pmnuCompactJson.Visible := ivAlways; pmnuFormatXML.Visible := ivNever; pmnuCompactXML.Visible := ivNever; end else if ActiveEditor.Highlighter = SynXMLSyn then begin pmnuFormatJsonContent.Visible := ivNever; pmnuCompactJson.Visible := ivNever; pmnuFormatXML.Visible := ivAlways; pmnuCompactXML.Visible := ivAlways; end else begin pmnuFormatJsonContent.Visible := ivNever; pmnuCompactJson.Visible := ivNever; pmnuFormatXML.Visible := ivNever; pmnuCompactXML.Visible := ivNever; end; if ActiveEditor = frmBiggerEditor.EditorBiggerEditor then begin pmnuBiggerEditor.Visible := ivNever; end else begin pmnuBiggerEditor.Visible := ivAlways; end; end; procedure TfrmMain.pmnuFormatJsonContentClick(Sender: TObject); var ja: TJsonArray; jo: TJsonObject; begin if ActiveEditor.Lines.Count > 0 then begin if Length(ActiveEditor.Lines[0]) > 0 then begin if ActiveEditor.Lines[0][1] = '{' then begin jo := TJsonObject.Create; Screen.Cursor := crHourGlass; try try jo.FromJSON(ActiveEditor.Lines.Text); except on E: Exception do begin ShowError(E.Message); end; end; UpdateEditor(jo.ToJSON(False)); finally Screen.Cursor := crDefault; jo.Free; end; end else if ActiveEditor.Lines[0][1] = '[' then begin ja := TJsonArray.Create; Screen.Cursor := crHourGlass; try try ja.FromJSON(ActiveEditor.Lines.Text); except on E: Exception do begin ShowError(E.Message); end; end; UpdateEditor(ja.ToJSON(False)); finally Screen.Cursor := crDefault; ja.Free; end; end; end; end; end; procedure TfrmMain.pmnuFormatXMLClick(Sender: TObject); var xml: TRwDomDocument; begin xml := TRwDOMDocument.Create; try Screen.Cursor := crHourGlass; xml.LoadXML(ActiveEditor.Lines.Text); UpdateEditor(xml.FormattedXML); finally xml.Free; Screen.Cursor := crDefault; end; end; procedure TfrmMain.Run; var tick: Cardinal; begin FHTTP := TIdHTTP.Create(nil); FHTTP.IOHandler := SSLIOHandlerSocketOpenSSL; FHTTP.HTTPOptions := []; FHTTP.HandleRedirects := True; FHTTP.AllowCookies := True; FHTTP.Request.UserAgent := 'Simnet REST Debugger/' + GetAppVersion; try EditorContent.Lines.Clear; EditorResponseHeaders.Lines.Clear; Screen.Cursor := crHourGlass; tick := GetTickCount; try FHTTP.Request.CustomHeaders.Clear; FHTTP.Request.CustomHeaders.AddStrings(EditorExtraHeaders.Lines); if chkUseBasicAuth.Checked then begin FHTTP.Request.Username := edtAuthUsername.Text; FHTTP.Request.Password := edtAuthPassword.Text; FHTTP.Request.BasicAuthentication := True; end else begin FHTTP.Request.BasicAuthentication := False; FHTTP.Request.Username := ''; FHTTP.Request.Password := ''; end; if comboMethod.ItemIndex = 0 then begin // GET DoGETRequest(mtGET); end else if comboMethod.ItemIndex = 1 then begin // POST DoPOSTRequest(mtPOST); end else if comboMethod.ItemIndex = 2 then begin // PATCH DoPOSTRequest(mtPATCH); end else if comboMethod.ItemIndex = 3 then begin // PUT DoPOSTRequest(mtPUT); end else if comboMethod.ItemIndex = 4 then begin // DELETE DoGETRequest(mtDELETE); end; finally ActiveEditor := EditorContent; EditorContent.SetFocus; if Pos('application/json', FHTTP.Response.ContentType) > 0 then begin EditorContent.Highlighter := SynJSONSyn; if chkResponseAutoformat.Checked then begin pmnuFormatJsonContentClick(nil); end; end else if Pos('text/xml', FHTTP.Response.ContentType) > 0 then begin EditorContent.Highlighter := SynXMLSyn; if chkResponseAutoformat.Checked then begin pmnuFormatXMLClick(nil); end; end else if Pos('text/html', FHTTP.Response.ContentType) > 0 then begin EditorContent.Highlighter := SynHTMLSyn; end else begin EditorContent.Highlighter := nil; end; dxStatusBarResponse.Panels[0].Text := FormatFloat('Response Time: 0, ms', GetTickCount - tick); dxStatusBarResponse.Panels[1].Text := 'Response Code: ' + FHTTP.ResponseCode.ToString; dxStatusBarResponse.Panels[2].Text := 'Response Content: ' + FHTTP.Response.ContentType; Screen.Cursor := crDefault; end; finally FHTTP.Free; end; end; procedure TfrmMain.SavePreset; var preset: string; idx: Integer; begin idx := comboPresets.Properties.Items.IndexOf(FActivePreset); if idx > -1 then begin preset := 'Preset_' + FPresetsList.Names[idx]; FProjectConfig.WriteString(preset, 'BaseURL', edtBaseURL.Text); FProjectConfig.WriteInteger(preset, 'Method', comboMethod.ItemIndex); FProjectConfig.WriteString(preset, 'URLParams', EditorURLParams.Lines.Text); FProjectConfig.WriteString(preset, 'PostData', EditorPostData.Lines.Text); FProjectConfig.WriteString(preset, 'ExtraHeaders', EditorExtraHeaders.Lines.Text); FProjectConfig.WriteString(preset, 'PostDataContentType', comboPostContentType.Text); FProjectConfig.WriteBool(preset, 'ResponseAutoFormat', chkResponseAutoformat.Checked); FProjectConfig.WriteBool(preset, 'UseBasicAuth', chkUseBasicAuth.Checked); FProjectConfig.WriteString(preset, 'AuthUsername', TEncryptStr.EncryptString(edtAuthUsername.Text)); FProjectConfig.WriteString(preset, 'AuthPassword', TEncryptStr.EncryptString(edtAuthPassword.Text)); FProjectConfig.WriteInteger('Presets', 'LastUsed', idx); end; end; procedure TfrmMain.SaveProject; begin SavePreset; FProjectConfig.WriteInteger('Presets', 'ConfigVersion', iCurrentConfigVersion); FProjectConfig.Save; end; procedure TfrmMain.SaveProjectAs(const LoadAfterSave: Boolean); var fname: string; begin SaveDialog.InitialDir := MainConfig.ReadString('Dialogs', 'LastSaveDir', ExtractFilePath(ParamStr(0))); if SaveDialog.Execute then begin fname := SaveDialog.FileName; MainConfig.WriteString('Dialogs', 'LastSaveDir', ExtractFilePath(fname)); fname := ChangeFileExt(fname, ''); fname := fname + '.rdproj'; SaveProject; CopyFile(PChar(FProjectConfig.FileName), PChar(fname), False); if LoadAfterSave then begin FIsTempProjectLoaded := False; FActiveProject := fname; LoadProject; end; end; end; procedure TfrmMain.ShowError(const AErrorText: string); begin MessageDlg(AErrorText, mtError, [mbOK], 0); end; procedure TfrmMain.ShowSearchReplaceDialog(AReplace: Boolean); begin with frmSearch do begin // assign search options SearchBackwards := gbSearchBackwards; SearchCaseSensitive := gbSearchCaseSensitive; SearchFromCursor := gbSearchFromCaret; SearchInSelectionOnly := gbSearchSelectionOnly; // start with last search text SearchText := gsSearchText; if gbSearchTextAtCaret then begin // if something is selected search for that text if ActiveEditor.SelAvail and (ActiveEditor.BlockBegin.Line = ActiveEditor.BlockEnd.Line) then SearchText := ActiveEditor.SelText else SearchText := ActiveEditor.GetWordAtRowCol(ActiveEditor.CaretXY); end; SearchTextHistory := gsSearchTextHistory; SearchWholeWords := gbSearchWholeWords; if ShowModal = mrOK then begin gbSearchBackwards := SearchBackwards; gbSearchCaseSensitive := SearchCaseSensitive; gbSearchFromCaret := SearchFromCursor; gbSearchSelectionOnly := SearchInSelectionOnly; gbSearchWholeWords := SearchWholeWords; gbSearchRegex := SearchRegularExpression; gsSearchText := SearchText; gsSearchTextHistory := SearchTextHistory; FSearchFromCaret := gbSearchFromCaret; if gsSearchText <> '' then begin DoSearchReplaceText(AReplace, gbSearchBackwards); FSearchFromCaret := TRUE; end; end; end; end; end.