STContains - a sublime text style contains in Cw

SublimeText has this amazing navigation tool, part of which is based on a very powerful contains behavior.

It takes each character you’ve typed into your filter, and it looks for those characters in the potential matches by looking for the characters in the same order, but they don’t have to be next to each other (and it’s case insensitive).

So typing: abf.c will match things like ABFile.clw (bold letters show matches).

To use it

  BIND('STContains'          , STContains)
  BIND('mat:Description'  , mat:Description) !<-- specific to my example
  BIND('lcl:Filter_Actual', lcl:Filter_Actual)  !<-- specific to my example

and

BRW1::View:Browse{Prop:Filter} = |
     CLIP( BRW1::View:Browse{Prop:Filter} )  & | 
       'AND STContains( lcl:Filter_Actual, mat:Description )'

 ! the CLIP might be redundant

and

 MAP 
    STContains(STRING xLookFor, STRING xSearchIn ),LONG 
 END

and

STContains              PROCEDURE(STRING xLookFor, STRING xSearchIn )!,LONG 
      !Returns 0, if not all chars match (in order)
      !Returns index of the last matching character (when all match)
Answer    LONG !no ,AUTO
CurrChar  LONG,AUTO
   CODE
   xLookFor  = UPPER(xLookFor )
   xSearchIn = UPPER(xSearchIn)

   LOOP CurrChar = 1 TO LEN(CLIP(xLookFor))
     Answer = INSTRING( xLookFor[CurrChar], xSearchIn, 1, Answer + 1)
     IF Answer = 0 THEN BREAK END
   END

   !v--- early version to prove the whole concept worked    
   !Answer = INSTRING( CLIP(xLookFor), UPPER(xSearchIn), 1, 1) 
   !Assert(0,eqDBG&'STContains Answer['& Answer &'] = xLookFor['& xLookFor &'] xSearchIn['& xSearchIn &']')

   RETURN Answer
4 Likes

Nice, but I do not know how / were to use it. Maybe someone can make a small demo app with this ? :wink:

One thing to keep in mind, if you are using a SQL database then using STContains will cause the filtering to become local and execute on the client workstation instead of the SQL Server. This could create a performance issue depending on the amount of data.

Rick

Nice work Mark.

just wondering if the ‘AND’ is a problem where the filter was otherwise blank?

two possible fixes (if it is in fact a problem)

would be replacing

BRW1::View:Browse{Prop:Filter} = |
     CLIP( BRW1::View:Browse{Prop:Filter} )  & | 
       'AND STContains( lcl:Filter_Actual, mat:Description )'

with either

if BRW1::View:Browse{Prop:Filter}
  BRW1::View:Browse{Prop:Filter} = |
     CLIP( BRW1::View:Browse{Prop:Filter} )  & | 
       'AND STContains( lcl:Filter_Actual, mat:Description )'
else
  BRW1::View:Browse{Prop:Filter} = |
       'STContains( lcl:Filter_Actual, mat:Description )'
end

or perhaps

BRW1::View:Browse{Prop:Filter} = |
     choose(~BRW1::View:Browse{Prop:Filter}, '', |
       CLIP( BRW1::View:Browse{Prop:Filter} ) & ' AND ') & | 
       'STContains( lcl:Filter_Actual, mat:Description )'

cheers

Geoff R

I mentioned Mark’s STContains in my recent presentation on Search at CIDC 2017 in Orlando Florida.

It is otherwise known as “subsequence”.

A slightly optimised version I used basically replaces instring with memChr for searching for a single character.

Note you need to prototype MemChr in the global map, I use:

  MODULE('whatever...')
    MemChr(ulong buf, long c, unsigned count), long, name('_memchr')
  END


vitSTContainsSimple  PROCEDURE  (*String SearchFor, *String SearchIn) !,LONG 

! Geoff Robinson May 2017
! based on idea from Mark Goldberg which in turn was based on a facility in Sublime Text search where you 
! search to find a match which contains the letters in the correct order (but my have extra letters before,
! after or in-between). eg. Search ANDSN might match ANDERSON and SANDERSEN (among others)
! https://clarionhub.com/t/stcontains-a-sublime-text-style-contains-in-cw/244
! returns TRUE for match or FALSE if no match   
! a match is where all chars are found within the string in the same order
! NB. above example ANDSN also matches "PITTMAN AND PARTNERS ACCOUNTING" so may need more work on splitting words etc

pos       LONG,auto
addr      LONG,auto

  CODE

  if ~SearchFor or ~SearchIn then return FALSE.

  addr = address(SearchIn)
  LOOP pos = 1 TO size(SearchFor)
    addr = MemChr(addr, val(SearchFor[pos]), size(SearchIn) + address(SearchIn) - addr) 
    if addr = 0 then return FALSE.
    addr += 1        
  END 
  
  return TRUE
1 Like

Thanks Geoff,

Have you run any benchmarks to compare MemChr vs. INSTRING ?

Hi Mark

I did a lot of work on searching about 18 or 20 years ago and found at the time that MemChr was very fast in comparison with Instring. Much later I won a Clarion magazine mug for the fastest code in a competition Dave Harms ran where I used MemChr. It is quite possible that Instring has improved since then as I have not done any recent comparisons.

However here I am only interested in whether the character is found or not - hence there is no need to convert to a position in the string (as would be required by instring) so I would imagine it MemChr will still be faster.

cheers

Geoff R