Carlo Barazzetta 116 Posted October 17, 2023 I'm searching for an "autocomplete editing" component (showing a list of possibilities) of available directories/filenames based on typed text, for an OpenSource project, so I need an OpenSource component that makes this work... Any suggestion? Thanks. Carlo Share this post Link to post
dummzeuch 1505 Posted October 17, 2023 (edited) 1 hour ago, Carlo Barazzetta said: I'm searching for an "autocomplete editing" component (showing a list of possibilities) of available directories/filenames based on typed text, for an OpenSource project, so I need an OpenSource component that makes this work... Any suggestion? Not a component but a function that adds this functionality to any TEdit: https://blog.dummzeuch.de/2019/04/28/autocompletion-for-tedits-revisited/ Edited October 17, 2023 by dummzeuch 2 Share this post Link to post
Carlo Barazzetta 116 Posted October 18, 2023 (edited) I've some problems using dzlib with Delphi11: 1) An AV inside TEnumStringFiles.Next 2) don't compile with 64bit platform Is this version the latest? https://sourceforge.net/projects/dzlib/ Thanks. Carlo Edited October 18, 2023 by Carlo Barazzetta Share this post Link to post
PeterBelow 238 Posted October 18, 2023 On 10/17/2023 at 11:02 AM, Carlo Barazzetta said: I'm searching for an "autocomplete editing" component (showing a list of possibilities) of available directories/filenames based on typed text, for an OpenSource project, so I need an OpenSource component that makes this work... Any suggestion? Thanks. Carlo Does a TCombobox, with the Items list filled with the available files, not fit your need? Share this post Link to post
Remy Lebeau 1396 Posted October 18, 2023 (edited) On 10/17/2023 at 2:58 AM, dummzeuch said: Not a component but a function that adds this functionality to any TEdit: https://blog.dummzeuch.de/2019/04/28/autocompletion-for-tedits-revisited/ Why implement a custom IEnumStrings class for directories/files? There is already a pre-made data source provided by the OS for that exact purpose - CLSID_ACListISF, which can be created with CoCreateInstance(), configured via IACList2::SetOptions(), and then passed to IAutoComplete::Init(). Edited October 18, 2023 by Remy Lebeau Share this post Link to post
dummzeuch 1505 Posted October 18, 2023 (edited) 1 hour ago, Remy Lebeau said: Why implement a custom IEnumStrings class for directories/files? There is already a pre-made data source provided by the OS for that exact purpose - CLSID_ACListISF, which can be created with CoCreateInstance(), configured via IACList2::SetOptions(), and then passed to IAutoComplete::Init(). Does that one also allow pre-filtering the filenames? e.g. If I only want to select from GX_*.pas files, or those matching *.dbf;*.dat ? I already had a similar function for filenames and directories without filtering using SHAutoComplete, but the filtering was important for me. (Short answer: I didn't know about this) Edited October 18, 2023 by dummzeuch Share this post Link to post
Remy Lebeau 1396 Posted October 18, 2023 30 minutes ago, dummzeuch said: Does that one also allow pre-filtering the filenames? e.g. If I only want to select from GX_*.pas files, or those matching *.dbf;*.dat ? Ah. No, it does not. In that case, you would indeed need a custom enumerator. Share this post Link to post
superkan619 0 Posted August 5 It's an old post but, one solution comes to my mind - JvLookupAutoComplete from Jedi VCL library. It autocompletes text in a TEdit based on a ListBox of strings. Share this post Link to post
dummzeuch 1505 Posted August 6 (edited) On 10/18/2023 at 1:22 PM, Carlo Barazzetta said: I've some problems using dzlib with Delphi11: 1) An AV inside TEnumStringFiles.Next 2) don't compile with 64bit platform Is this version the latest? https://sourceforge.net/projects/dzlib/ Sorry, I totally missed this post. I guess answering nearly a year later is kind of limited use for you, but anyway: Regarding 2: There is only very limited support for Win64 (and none at all for non windows platforms) Regarding 1: We (I and my coworkers) use dzlib primarily with Delphi 2007 and Delphi 10.2. There is very limited testing for other Delphi versions. Some part of it is used in GExperts so that one gets wider testing, but TEnumStringFiles is not part of that. Edit: I just tried it with Delphi 11 and I can reproduce the problem. It also happens in Delphi 12. It does not happen in Delphi 10.2 and 2007 so I guess something in the RTL changed. No solution yet. Yes, https://sourceforge.net/projects/dzlib/ is the latest version, or rather https://blog.dummzeuch.de/dzlib/ which links there and will be updated should I ever decide to host the code elsewhere. Edited August 6 by dummzeuch Share this post Link to post
dummzeuch 1505 Posted August 11 On 8/6/2024 at 8:21 AM, dummzeuch said: I just tried it with Delphi 11 and I can reproduce the problem. It also happens in Delphi 12. It does not happen in Delphi 10.2 and 2007 so I guess something in the RTL changed. No solution yet. The code works fine up until Delphi 10.4 and fails from Delphi 11 on. If I remember correctly, the largest change between these versions was by monitor DPI support, but I'm not sure wether that was for applications or the IDE or both. Share this post Link to post
dummzeuch 1505 Posted August 11 Fixed the problem in revision #1737. Something must have changed in the way Delphi handles WideStrings between Delphi 10.4 and Delphi 11. The hack of setting a WideStiring variable to NIL no longer cleared it without decrementing the reference counter, but apparently freed that string. Instead the code now allocates new memory and moves the content to that new memory. Or possibly it never actually worked but for some reason didn't raise an exception until Delphi 11. While I was at it, I moved the boiler plate code to a new class TEnumStringsHelper so all descendants now only need to implement two simple methods rather than four. Share this post Link to post
Remy Lebeau 1396 Posted August 11 (edited) WideString is not reference counted, and setting a WideString to nil via a Pointer typecast works just fine to release ownership of the BSTR without freeing its memory. Always has. And that has not changed. Looking at your changes, I think you are simply misusing WideString to begin with. TEnumStringHelper.Next() should look more like this instead: function TEnumStringHelper.Next(celt: Integer; out elt; pceltFetched: PLongint): HResult; type TPointerList = array[0..0] of Pointer; var i: Integer; s: string; begin i := 0; while (i < celt) and TryGetNext(s) do begin // this directive is needed because the array is declared as having only // 1 element but celt may be > 1! The alternative is to either declare // TPointerList with a rediculous high number of elements (like the RTL // does in some places), eg: // // type // TPointerList = array[0..MaxInt div SizeOf(Pointer) - 1] of Pointer; // // or else just use pointer arithmetic on elt directly... {$RANGECHECKS OFF} // Ensure the WideString is nil before assiging a String to it... TPointerList(elt)[i] := nil; WideString(TPointerList(elt)[i]) := s; Inc(i); end; if pceltFetched <> nil then pceltFetched^ := i; if i = celt then Result := S_OK else Result := S_FALSE; end; Alternatively: function TEnumStringHelper.Next(celt: Integer; out elt; pceltFetched: PLongint): HResult; type TWideStringList = array[0..0] of WideString; var i: Integer; s: string; begin i := 0; while (i < celt) and TryGetNext(s) do begin // see notes above... {$RANGECHECKS OFF} Pointer(TWideStringList(elt)[i]) := nil; TWideStringList(elt)[i] := s; Inc(i); end; if pceltFetched <> nil then pceltFetched^ := i; if i = celt then Result := S_OK else Result := S_FALSE; end; Also, your TEnumStringHelper.Skip() is just plain wrong. If celt is <= 1 then you get stuck in an endless loop retrieving strings until the enum is exhausted, and if celt is > 1 then you exit prematurely after 1 string is retrieved. It should look more like this instead: function TEnumStringHelper.Skip(celt: Integer): HResult; var i: Integer; s: string; begin i := 0; while (i < celt) and TryGetNext(s) do Inc(i); if i = celt then Result := S_OK else Result := S_FALSE; end; Edited August 11 by Remy Lebeau Share this post Link to post
dummzeuch 1505 Posted August 12 12 hours ago, Remy Lebeau said: TEnumStringHelper.Next() should look more like this instead: Unfortunately both your proposed changes to the code of .Next still raise the same "System exception (code 0xc0000374) at 0x77026e13" with "77026db3 ntdll.RtlIsZeroMemory + 0x93" at the top of the call stack" as my original code did in Delphi 12. My changed code works in all versions of Delphi from 2007 up. 12 hours ago, Remy Lebeau said: Also, your TEnumStringHelper.Skip() is just plain wrong. If celt is <= 1 then you get stuck in an endless loop retrieving strings until the enum is exhausted, and if celt is > 1 then you exit prematurely after 1 string is retrieved. You are right with the problem in Skip though. I should have read the description more carefully. Share this post Link to post
Remy Lebeau 1396 Posted August 12 (edited) 6 hours ago, dummzeuch said: Unfortunately both your proposed changes to the code of .Next still raise the same "System exception (code 0xc0000374) at 0x77026e13" with "77026db3 ntdll.RtlIsZeroMemory + 0x93" at the top of the call stack" as my original code did in Delphi 12. My changed code works in all versions of Delphi from 2007 up. I tested the code I showed with Delphi 12.1 before I posted it, and it worked fine. And it should also work fine in earlier versions, as handling of WideString has not changed over the years. Which makes me wonder if there is another problem with your larger project and .Next is just a victim of circumstance? This feels like a classic symptom of "undefined behavior" being invoked in earlier code. Quote Edited August 12 by Remy Lebeau Share this post Link to post
dummzeuch 1505 Posted August 12 1 hour ago, Remy Lebeau said: I tested the code I showed with Delphi 12.1 before I posted it, and it worked fine. And it should also work fine in earlier versions, as handling of WideString has not changed over the years. Which makes me wonder if there is another problem with your larger project and .Next is just a victim of circumstance? This feels like a classic symptom of "undefined behavior" being invoked in earlier code. Hm. my "larger code" is the test program here: https://svn.code.sf.net/p/dzlib/code/dzlib/trunk/tests/AutoCompleteTest Which isn't much more than a form with a few TEdits and calls to the various TEdit_AutoAutoCompleteXxx procedures in the constructor: constructor Tf_AutoCompleteTest.Create(_Owner: TComponent); var sl: TStringList; begin inherited; TEdit_ActivateAutoCompleteDirectories(ed_AutoCompleteDirs); sl := TStringList.Create; try sl.Add('one'); sl.Add('two'); sl.Add('three'); sl.Add('four'); sl.Add('five'); sl.Add('six'); sl.Add('seven'); sl.Add('eight'); sl.Add('nine'); sl.Add('ten'); TEdit_SetAutoCompleteStringList(ed_AutoCompleteStrings, sl); finally FreeAndNil(sl); end; TEdit_ActivateAutoCompleteFiles(ed_AutoCompleteDfmFiles, '*.dfm'); TEdit_ActivateAutoCompleteFiles(ed_AutoCompleteFilesCallback, AutocompleteFilter); // These masks are passed to Masks.MatchesMask, so they allow something more than just '*' and '?' // wildcards, e.g. '[a-z]_*.dpr' would also work, which matches 'a_blablub.dpr'. TEdit_ActivateAutoCompleteFiles(ed_AutoCompleteFilesMasks, ['*.dpr', '*.pas']); TEdit_ActivateAutoCompletePath(ed_AutoCompletePath); end; procedure Tf_AutoCompleteTest.AutocompleteFilter(_Sender: TObject; const _Filename: string; var _Accept: Boolean); var ext: string; begin ext := TFileSystem.ExtractFileExtFull(_Filename); _Accept := SameText(ext, '.pas') or SameText(ext, '.dfm'); end; Could you please post the code you used to test your .Next implementation(s)? Share this post Link to post
Remy Lebeau 1396 Posted August 12 (edited) 6 hours ago, dummzeuch said: Could you please post the code you used to test your .Next implementation(s)? This is the complete code. No crashes or leaks: program WideStringTest; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, Winapi.ActiveX; type TEnumStringTestBase = class(TInterfacedObject, IEnumString) function Next(celt: Longint; out elt; pceltFetched: PLongint): HResult; virtual; stdcall; abstract; function Skip(celt: Longint): HResult; stdcall; function Reset: HResult; stdcall; function Clone(out enm: IEnumString): HResult; stdcall; end; TEnumStringTest1 = class(TEnumStringTestBase) function Next(celt: Longint; out elt; pceltFetched: PLongint): HResult; override; end; TEnumStringTest2 = class(TEnumStringTestBase) function Next(celt: Longint; out elt; pceltFetched: PLongint): HResult; override; end; function TEnumStringTestBase.Skip(celt: Longint): HResult; stdcall; begin Result := S_FALSE; end; function TEnumStringTestBase.Reset: HResult; stdcall; begin Result := S_OK; end; function TEnumStringTestBase.Clone(out enm: IEnumString): HResult; stdcall; begin Result := E_NOTIMPL; end; function TEnumStringTest1.Next(celt: Longint; out elt; pceltFetched: PLongint): HResult; type TPointerList = array[0..0] of Pointer; var i: Integer; begin i := 0; while (i < celt) do begin {$RANGECHECKS OFF} TPointerList(elt)[I] := nil; WideString(TPointerList(elt)[I]) := 'test'+IntToStr(I); Inc(i); end; if pceltFetched <> nil then pceltFetched^ := i; if i = celt then Result := S_OK else Result := S_FALSE; end; function TEnumStringTest2.Next(celt: Longint; out elt; pceltFetched: PLongint): HResult; type TWideStringList = array[0..0] of WideString; var i: Integer; begin i := 0; while (i < celt) do begin {$RANGECHECKS OFF} Pointer(TWideStringList(elt)[I]) := nil; TWideStringList(elt)[I] := 'test'+IntToStr(I); Inc(i); end; if pceltFetched <> nil then pceltFetched^ := i; if i = celt then Result := S_OK else Result := S_FALSE; end; var Enum: IEnumString; arr1: array[0..4] of TBstr; arr2: array[0..4] of WideString; I: Integer; begin Enum := TEnumStringTest1.Create; Enum.Next(Length(arr1), arr1, nil); for I := Low(arr1) to High(arr1) do WriteLn(arr1[i]); for I := Low(arr1) to High(arr1) do SysFreeString(arr1[i]); WriteLn; Enum := TEnumStringTest2.Create; Enum.Next(Length(arr2), arr2, nil); for I := Low(arr2) to High(arr2) do WriteLn(arr2[i]); ReadLn; end. Edited August 12 by Remy Lebeau 1 Share this post Link to post