Date: 02-16-2022
Return to Index
created by gbSnippets
'One of the more useful features of a modern day editor is the ability to give
'the users suggestions/options for the text they are about to type - syntax
'information, function arguments, and member information. This is called
'Intellisense by Microsoft.
'An additional feature would be simply 'word completion', where the editor provides
'suggested spelling/text for completing a started word - essentially an automatic
'letter-by-letter spell-check with suggestions for completing the word being typed.
'This snippet deals with Intellisense, where the suggestions are for what follows
'a word that has just been typed.
'Primary Code:
'For length reasons, the primary code is given only once in the compilable example
'below. But a general description of the process is provided here.
'As each letter is typed the letters to the left are scanned for the presence of up
'to 3 words, corresponding to the maximum number of words in a multipleword
'PowerBASIC statement.
'However, the scanning only takes place when the character to the left of the cursor
'is one of several marker characters - a space, open parenthesis, period, or close
'parenthesis. These four characters mark the possible start/end of a PowerBASIC
'word or phrase that Intellisense will recognize. The presence of any other character
'closes any visible Intellisense display of information.
'When a marker charater is found and the (up to) 3 words to the left are scanned,
'a prioritized list of 5 searches is made to determine if the words are found in
'the Intellisense libraries.
'There are two types of words/phrases - those that will simply be followed by a
'list of arguments (the 'syntax') or those which will be followed by optional members.
'The 5 libraries are:
' - single word Keywords - single word Keywords with Members
' - dual word Keyword phrases - dual word Keyword phrases with Members
' - triple word Keyword phrases
'These correspond to the PowerBASIC statements, which consist of 1-3 keywords that
'preceed an argument list or 1-2 keywords which preceed member options.
'If a word/phrase is found that supports trailing arguments (syntax), the argument list
'is presented in a small label just below the cursor.
'If a word/phrase is found that supports trailing members, the member list is presented
'in a listbox just below the cursor.
'When a word/phrase with members is found, the members are displayed in a popup
'listobx. If the word/phrase has syntax, the syntax is shown in a popup label.
'With an Intellisense listbox or label shown, the user may use the following keys to
'take action.
' ESC - hide the label/listbox
' TAB - insert the argument list or selected member at the cursor
' ENTER - insert the argument list or selected member and move cursor to a new line
'The Intellisense implementation in the compilable example below has the
'following limitations, which may be different than some implementations of
'Intellisense available in other editors.
'1. Dim x as MyType
'This snippet cannot automatically (at run time) determine MyType members
'unless they have been manually placed in the reference files. Variables
'dimensioned as structure types are not recognized, i.e., popup member lists
'are not presented.
'2. ListBox
'Once the listbox is displayed, a selection must be mode by pressing
'TAB or Enter, or else the ESC can be used to removed the listbox from view.
'Pressing letter keys will select an item from the list but the typed letters
'will not appear in the edit control.
'3. Argument Highlighting
'As arguments to a keyword/phrase are typed, Microsoft Intellisense changes
'the content of the popup syntax label - bolding the argument currently being
'typed. The snippet below do not provide the bolding feature.
'In addition to the code in the callback and subclassed window procedures, there are
'several different subroutines which combine to provide the Intellisense features.
'1. Intellisense -coordinates all of the other routines
'2. CharToLeftOfCursor -returns the single character to the left of the cursor
'3. TestLeftChar -takes action depending on what CharToLeftOfCursor Returns
'4. WordsToLeft -returns the (up to) 3 words preceeding the cursor
'5. CloseIntellisense -hide label/listbox, reset all flags
'6. InsertText -place the label/listbox text into the RichEdit control
'7. LoadRef -loads the reference files
'8. BinaryRefSearch -common routine to search all 5 reference files
'9. Modify Syntax -modifies how syntax is displayed, depending on context of user input
'10. DisplaySyntaxLabel -shows argument list (syntax) for the preceeding 1-3 words/phrase
'11. DisplaySyntaxListBox -shows available Members for the preceeding 1-2 words/phrase
'12. NewListBoxProc -detexts pressing RETURN, ESC, and TAB keys in ListBox
'13. NewRichEditProc -detects pressing RETURN key in RichEdit control
'In addition to the source code below, the following text data files are required.
'Just put these files into the same folder as the EXE. These are now included as
'part of the gbSnippets distribution.
' http://www.garybeene.com/files/word3_short.txt
' http://www.garybeene.com/files/word2_short.txt
' http://www.garybeene.com/files/word1_short.txt
' http://www.garybeene.com/files/members1.txt
' http://www.garybeene.com/files/members2.txt
' http://www.garybeene.com/files/powerbasic.syn
'Compiler Comments:
'This code is written to compile with PBWin10. To compile with PBWin9,
'add this line:
#Include "CommCtrl.inc"
'Compilable Example: (Jose Includes)
#Compiler PBWin 10
#Compile EXE
#Dim All
%Unicode=1
#Include "win32api.inc"
#Include "RichEdit.inc"
Global Ref_MemTerm1() As String, Ref_MemMember1() As String
Global Ref_MemTerm2() As String, Ref_MemMember2() As String
Global Ref_Term1() As String, Ref_Desc1() As String, Ref_Syntax1() As String
Global Ref_Term2() As String, Ref_Desc2() As String, Ref_Syntax2() As String
Global Ref_Term3() As String, Ref_Desc3() As String, Ref_Syntax3() As String
Global hDlg as DWord, hRichEdit as DWord, LabelVisible&, ListBoxVisible&, hListBox As DWord
Global OldListBoxProc&, PI As CharRange, OldRichEditProc&, CancelIntellisense&
%ID_RichEdit = 501 : %ID_Label = 502 : %ID_ListBox = 503 : %ID_Button = 504 : %ID_Button2 = 505
Function PBMain() As Long
Local style&, buf$
Dim Ref_Term1(0), Ref_Desc1(0), Ref_Syntax1(0)
Dim Ref_Term2(0), Ref_Desc2(0), Ref_Syntax2(0)
Dim Ref_Term3(0), Ref_Desc3(0), Ref_Syntax3(0)
Dim Ref_MemTerm1(0), Ref_MemMember1(0)
Dim Ref_MemTerm2(0), Ref_MemMember2(0)
buf$ = "Type any PowerBASIC code."
style& = %WS_Child Or %WS_Visible Or %ES_MultiLine Or %WS_VScroll Or %ES_AutoHScroll _
Or %WS_HScroll Or %ES_AutoVScroll Or %ES_WantReturn Or %ES_NoHideSel Or %WS_TabStop
Dialog New Pixels, 0, "Test Code",300,300,300,160, %WS_OverlappedWindow To hDlg
LoadRef "word1_short.txt", Ref_Term1(), Ref_Syntax1()
LoadRef "word2_short.txt", Ref_Term2(), Ref_Syntax2()
LoadRef "word3_short.txt", Ref_Term3(), Ref_Syntax3()
LoadRef "members1.txt", Ref_memTerm1(), Ref_memMember1()
LoadRef "members2.txt", Ref_memTerm2(), Ref_memMember2()
LoadLibrary("riched32.dll") : InitCommonControls
Control Add Button, hDlg, %ID_Button, "Just for Show",20,10,80,20
Control Add Button, hDlg, %ID_Button, "Just for Show",110,10,80,20
Control Add "RichEdit", hDlg, %ID_RichEdit, buf$,20,40,260,100, style&, %WS_EX_ClientEdge
Control Add Label, hDlg, %ID_Label, "tooltip",60,60,100,15, %WS_Border
Control Set Color hDlg, %ID_Label, %Black, %RGB_LightYellow
Control Add ListBox, hDlg, %ID_ListBox, ,60,60,100,100, %WS_Border
Control Handle hDlg, %ID_RichEdit To hRichEdit
Control Handle hDlg, %ID_ListBox To hListBox
SendMessage hRichEdit, %EM_SETEVENTMASK, 0, %ENM_SELCHANGE Or %ENM_CHANGE Or %ENM_Link Or %ENM_KeyEvents
Dialog Show Modal hDlg Call DlgProc
End Function
CallBack Function DlgProc() As Long
Local temp$, P as CharRange
Select Case CB.Msg
Case %WM_InitDialog
PostMessage hRichEdit, %EM_SETSEL, 0, 0 'remove highlighting on startup
OldRichEditProc& = SetWindowLong(GetDlgItem(hDlg, %ID_RichEdit), %GWL_WndProc, CodePTR(NewRichEditProc))
OldListBoxProc& = SetWindowLong(GetDlgItem(hDlg, %ID_ListBox), %GWL_WndProc, CodePTR(NewListBoxProc))
CloseIntellisense
SetFocus hRichEdit
Case %WM_Destroy
SetWindowLong hRichEdit, %GWL_WNDPROC, OldRichEditProc& 'un-subclass
SetWindowLong hListBox, %GWL_WNDPROC, OldListBoxProc& 'un-subclass
Case %WM_Notify
Select Case CB.NmID
Case %ID_RichEdit
Select Case CB.Nmcode
Case %EN_SelChange
TestLeftChar
End Select
End Select
Case %WM_NEXTDLGCTL
Select Case GetFocus
Case hRichEdit 'captures TAB in RichEdit
If LabelVisible& Or ListBoxVisible& Then
InsertText : Function = 1 : Exit Function
End If
End Select
Case %WM_Command
Select Case CB.Ctl
Case %ID_RichEdit
If CB.Ctlmsg = %EN_SetFocus Then
P.cpmin = 0 : P.cpmax = 0 : SendMessage hRichedit, %EM_EXSETSEL, 0, VarPTR(P) 'highlight none
End If
Case %IdCancel 'pressing Escape
Select Case GetFocus 'gets the control which has the focus
Case hRichEdit : If LabelVisible& Or ListBoxVisible& Then CloseIntellisense 'ESC pressed in RichEdit
End Select
End Select
End Select
End Function
Sub TestLeftChar
Local temp$
temp$ = CharToLeftOfCursor
If temp$ = $spc Then
Intellisense $spc
ElseIf temp$ = "(" Then
Intellisense "("
ElseIf temp$ = ")" Then
If LabelVisible& Or ListBoxVisible& Then CloseIntellisense
ElseIf temp$ = "." Then
Intellisense "."
Else
CloseIntellisense
End If
End Sub
Function CharToLeftOfCursor() As String
Local P As CharRange, buf$, T as TextRange
SendMessage(hRichEdit, %EM_EXGetSel, 0, VarPTR(P)) 'caret position
T.Chrg.cpmin = P.cpmin-1 : T.Chrg.cpmax = P.cpmax : buf$ = " "
T.lpstrText = StrPTR(Buf$)
SendMessage hRichEdit, %EM_GetTextRange, ByVal 0, VarPTR(T) 'get text, specified char range or from selection
Function = buf$
End Function
Function WordsToLeft(w3$, w2$, w1$) As Long
Local iLine As Long, buf$, iStartPos&, iLineLength&, P As CharRange, iLeft&, iCount&
SendMessage(hRichEdit, %EM_EXGetSel, 0, VarPTR(P)) 'caret position
Decr p.cpmin
iLine = SendMessage(hRichEdit, %EM_ExLineFromChar, 0, -1) 'current line#
iStartPos& = SendMessage(hRichEdit, %EM_LineIndex, iLine, 0) 'position of 1st char in current line
iLineLength& = SendMessage(hRichEdit, %EM_LineLength, iStartPos&, 0) 'length of specified line
buf$ = Space$(iLineLength&)
SendMessage(hRichEdit, %EM_GetLine, iLine, StrPTR(buf$)) 'text of current line
w3$ = Mid$(buf$,1,P.cpmin-iStartPos&) 'text to left of caret
w3$ = Retain$(w3$, Any Chr$(65 to 90, 97 to 122, 48 to 57, $Spc, "$%?!"))
iCount& = ParseCount(w3$, " ")
w1$ = Parse$(w3$," ",iCount&)
w2$ = Parse$(w3$," ",iCount&-1)
w3$ = Parse$(w3$," ",iCount&-2)
End Function
Sub Intellisense(sChar$)
If CancelIntellisense& Then Exit Sub
Local sWord$, sSyntax$, iReturn&, w3$, w2$, w1$
SendMessage(hRichEdit, %EM_EXGetSel, 0, VarPTR(PI)) 'caret position at start of intellisense
WordsToLeft(w3$, w2$, w1$)
If Len(w3$) AND BinaryReferenceSearch(Build$(w3$,$spc,w2$,$spc,w1$), iReturn&, Ref_Term3(), Ref_Syntax3()) Then
'3 word sequence was found
sWord$ = Build$(w3$,$spc,w2$,$spc,w1$)
sSyntax$ = Ref_Syntax3(iReturn&)
DisplaySyntaxLabel (sSyntax$)
ElseIf Len(w2$) AND BinaryReferenceSearch(Build$(w2$,$spc,w1$), iReturn&, Ref_memTerm2(), Ref_memMember2()) Then
'2 word sequence was found
sWord$ = Build$(w2$,$spc,w1$)
sSyntax$ = Ref_memMember2(iReturn&)
DisplaySyntaxListbox (sSyntax$)
ElseIf Len(w2$) AND BinaryReferenceSearch(Build$(w2$,$spc,w1$), iReturn&, Ref_Term2(), Ref_Syntax2()) Then
'2 word sequence was found
sWord$ = Build$(w2$,$spc,w1$)
sSyntax$ = Ref_Syntax2(iReturn&)
DisplaySyntaxLabel (sSyntax$)
ElseIf Len(w1$) AND BinaryReferenceSearch(w1$, iReturn&, Ref_memTerm1(), Ref_memMember1()) Then
'1 word sequence was found
sWord$ = w1$
sSyntax$ = Ref_memMember1(iReturn&)
DisplaySyntaxListBox (sSyntax$)
ElseIf Len(w1$) AND BinaryReferenceSearch(w1$, iReturn&, Ref_Term1(), Ref_Syntax1()) Then
'1 word sequence was found
sWord$ = w1$
sSyntax$ = ModifySyntax(sChar$, Ref_Syntax1(iReturn&))
DisplaySyntaxLabel (sSyntax$)
Else
'no matches were found
If LabelVisible& Or ListBoxVisible& Then CloseIntellisense
End If
End Sub
Function ModifySyntax (sChar$, ByVal sSyntax$) As String
' If sChar$ = " " AND Left$(sSyntax$,1) = "(" Then 'optional way to skip leading (
' sSyntax$ = Mid$(sSyntax$, 2, Len(sSyntax$)-2)
If sChar$ = "(" AND Left$(sSyntax$,1) = "(" Then
sSyntax$ = Mid$(sSyntax$, 2) 'do not allow ((
ElseIf sChar$ = "(" AND Left$(sSyntax$,1) <> "(" Then
sSyntax$ = "" 'if sChar is (, then sSyntax must also start with (, otherwise, don't show sSyntax
End If
Function = sSyntax$
End Function
Sub CloseIntellisense
Control Show State hDlg, %ID_Label, %SW_Hide : LabelVisible& = 0
Control Show State hDlg, %ID_ListBox, %SW_Hide : ListBoxVisible& = 0
End Sub
Sub DisplaySyntaxLabel(sSyntax$)
Local P as Point
Control Set Text hDlg, %ID_Label, sSyntax$ 'put sSyntax in Label
Control Send hDlg, %ID_RichEdit, %EM_PosFromChar, VarPTR(P), PI.cpmin 'get xy coordinates of caret
Control Set Loc hDlg, %ID_Label, P.x+25, P.y+60 'assign position of label
Control Set Size hDlg, %ID_Label, Len(sSyntax$)*7,15
Control Show State hDlg, %ID_Listbox, %SW_Hide : ListBoxVisible& = 0 'hide listbox
Control Show State hDlg, %ID_Label, %SW_Show : LabelVisible& = 1 'show label
End Sub
Sub DisplaySyntaxListBox(sMembers$)
Local P as Point, i As Long
ListBox Reset hDlg, %ID_ListBox
Dim mList(ParseCount(sMembers$,".")-1) As String
Parse sMembers$,mList(),"."
For i = 0 to UBound(mList) : ListBox Insert hDlg, %ID_ListBox, 1, mList(i) : Next i
Control Send hDlg, %ID_RichEdit, %EM_PosFromChar, VarPTR(P), PI.cpmin 'get xy coordinates of caret
Control Set Loc hDlg, %ID_ListBox, P.x+25, P.y+60 'assign position of label
Control Show State hDlg, %ID_Label, %SW_Hide : LabelVisible& = 0 'hide label
Control Show State hDlg, %ID_ListBox, %SW_Show :ListBoxVisible& = 1 'show listbox
Control Set Focus hDlg, %ID_ListBox
ListBox Select hDlg, %ID_ListBox, 1
End Sub
Function BinaryReferenceSearch(ByVal sWord As String, iArrayPos&, ArrayTerm() As String, ArraySyntax() As String) As Long
Local Upper As Long, Lower As Long
Lower = LBound(ArrayTerm) : Upper = UBound(ArrayTerm) : sWord = LCase$(sWord)
'test boundary values
If sWord = ArrayTerm(Lower) Then iArrayPos& = Lower : Function = 1 : Exit Function
If sWord = ArrayTerm(Upper) Then iArrayPos& = Upper : Function = 1 : Exit Function
If sWord < ArrayTerm(Lower) Then iArrayPos& = Lower - 1 : Function = 0 : Exit Function
If sWord > ArrayTerm(Upper) Then iArrayPos& = Upper + 1 : Function = 0 : Exit Function
'loop through remaining entries until searchterm found, or it's determined that term is not in the array
Do Until (Upper <= (Lower+1))
iArrayPos& = (Lower + Upper) / 2
If sWord > ArrayTerm(iArrayPos&) Then
Lower = iArrayPos&
ElseIf sWord < ArrayTerm(iArrayPos&) Then
Upper = iArrayPos&
Else
Function = 1 : Exit Function
End If
Loop
End Function
Sub LoadRef (sFile$, ArrayTerm() As String, ArraySyntax() As String)
'load any of the 5 reference files - all use the same content format sWord:::::sSyntax
Local temp$, i As Long
Open sFile$ For Binary as #1 : Get$ #1, Lof(1), temp$ : Close
temp$ = RTrim$(temp$,$crlf)
ReDim ArrayTerm(ParseCount(temp$,$crlf)-1) As String, ArraySyntax(UBound(ArrayTerm)) As String
Parse temp$,ArrayTerm(),$crlf
For i = 0 to UBound(ArrayTerm)
ArraySyntax(i) = Parse$(ArrayTerm(i),":::::", 2)
ArrayTerm(i) = Parse$(ArrayTerm(i),":::::", 1)
Next i
End Sub
Sub InsertText
Local temp$
If LabelVisible& Then
Control Get Text hDlg, %ID_Label To temp$ 'get text
temp$ = temp$ + " "
Control Show State hDlg, %ID_Label, %SW_Hide : LabelVisible& = 0 'hide label
SendMessage hRichEdit, %EM_ReplaceSel, 0, StrPTR(temp$) 'put text in RichEdit
ElseIf ListBoxVisible& Then
CancelIntellisense& = %True
ListBox Get Text hDlg, %ID_ListBox To temp$ 'get text (selected item)
temp$ = temp$ + " "
Control Show State hDlg, %ID_ListBox, %SW_Hide : ListBoxVisible& = 0 'hide ListBox
SetFocus hRichEdit
SendMessage hRichEdit, %EM_EXSetSel, 0, VarPTR(PI) 'set cursor to new position
SendMessage hRichEdit, %EM_ReplaceSel, 0, StrPTR(temp$) 'put text in RichEdit
CancelIntellisense& = %False
End If
End Sub
Function NewListBoxProc(ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Select Case Msg
Case %WM_GETDLGCODE 'establish control by the RichEdit
Function = %DLGC_WANTALLKEYS
Exit Function
Case %WM_KeyDown 'WM_Char
Select Case wParam
Case %VK_Return
InsertText 'richedit will now have focus
keybd_event %VK_Return, 0, 0, 0 'send return key to hRichEdit
Case %VK_Escape
CancelIntellisense& = %True 'avoids firing Intellisense a second time before this loop is over
CloseIntellisense
SetFocus hRichEdit
SendMessage (hRichEdit, %EM_EXSetSel, 0, VarPTR(PI))
CancelIntellisense& = %False
Case %VK_Tab
InsertText
TestLeftChar 'the inserted text may actually be a keyword itself
End Select
End Select
Function = CallWindowProc(OldListBoxProc&, hWnd, Msg, wParam, lParam)
End Function
Function NewRichEditProc(ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Local iResult&
Select Case Msg
Case %WM_KeyDown 'WM_Char
Select Case wParam
Case %VK_Return
If LabelVisible& Or ListBoxVisible& Then InsertText 'allow to continue processing
End Select
End Select
Function = CallWindowProc(OldRichEditProc&, hWnd, Msg, wParam, lParam)
End Function
'gbs_00400
'Date: 03-10-2012
http://www.garybeene.com/sw/gbsnippets.htm