******************************************************************************** * Title: VariSense 2.0 * Author: Derek J. Kalweit(dkalweit@sensiblesoftware.com) * Description: This program belongs under an "ON KEY LABEL" in your development * environment. When triggered, it will automatically consult your declared * variables and determine what it can do to complete it for you if possible. * This can lead to a huge savings in typing, particularly if you routinely use * long, clear variables with very unique names. This is the functionality * available with a CTRL+SPACEBAR key combination in VC++, though VC++ is smart * enough to do this with member variables and functions as well(which this is * not). * Terms of use: Freeware. Feel free to use and modify for your needs. Send me * any useful/interesting enhancements so that I may include them in the main * version, if appropriate. ******************************************************************************** * * Latest version available at http://www.sensiblesoftware.com/ * * Version History: * 2.0(Released 2005-1-24): * o Adds support for "DEFINE CLASS" sensing(properties and function/procedure names) * o Fixes Varisense not working when keywords aren't all capitals * 1.2(Released 2003-2-28): * o Adds support for "DIMENSION" arrays * o Adds support for function parameters defined in parenthesis versus LPARAMETERS statement * 1.1(Released 2002-10-18): * o Added drop-down menu to pick ambigious variables * 1.0(Released 2002-10-8): * o Original release ******************************************************************************** * Special Thanks to: * Ed leafe(ed@leafe.com): * Contributed code to add drop-down menu for ambiguous variables * Bo Durban(wdurban@datascantech.com): * Contributed EditPos() function to get cursor position for drop-down menu ******************************************************************************** #define VARIABLE_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTYUVWXYZ0123456789_." SET LIBRARY TO (SYS(2004) + "FoxTools.fll") && foxtools is always in the foxpro dir, right? LOCAL hwndCode, laEnv[25] hwndCode = _WonTop() IF 1 == _EdGetEnv(hwndCode, @laEnv) Then IF 0 == laEnv[12] AND INLIST(laEnv[25], 0, 1, 8, 10, 12) Then LOCAL iPos, cVar, aMatches[1], iMatches iMatches = 0 iPos = laEnv[17] cVar = GetVariable(@hwndCode, @iPos) DO CASE CASE "this." == LOWER(LEFT(cVar, 5)) AND 1 == laEnv[25] * Let's remove the this. from our cVar, so our matching code works later on cVar = SUBSTR(cVar, 6) ******************************************************************************** * DJK 01/24/2005 10:06:02 AM: Adding support for define class properties... ******************************************************************************** * Well, we're trying to look up properties for the object. I'm going to * make this work with our own methods, too, 'cause why not... LOCAL lcProgText, laProgText[1], liCurrentLine, liLastLine, liLine, liClassStart, liClassEnd STORE 0 TO liClassStart, liClassEnd * First, let's find where our class starts and ends. Property definitions * can legally be above or below us. They must be out of any functions or * procedures, however... We have to make sure we only look inside the * current class, however, as there may be more than one in the open prg lcProgText = _EdGetStr(hwndCode, 0, laEnv[2]) && get all code in the file; this is the largest positive integer(2^31) liLastLine = ALINES(laProgText, lcProgText) liCurrentLine = _EdGetLNum(hWndCode, iPos) FOR liLine = liCurrentLine TO 1 STEP -1 IF "DEFINE CLASS " == UPPER(LEFT(WSTRIM(laProgText[liLine]), 13)) Then liClassStart = liLine EXIT ENDIF NEXT FOR liLine = liCurrentLine TO liLastLine STEP 1 IF "ENDDEFINE" == UPPER(LEFT(WSTRIM(laProgText[liLine]), 9)) Then liClassEnd = liLine EXIT ENDIF NEXT IF liClassStart > 0 AND liClassEnd > 0 Then * We're in a class-- we know where it starts, and where it ends-- let's go through and find our stuff LOCAL llInMethod, lcUpperLine, lcLine, lcFoundVar FOR liLine = liClassStart TO liClassEnd lcLine = WSTRIM(laProgText[liLine]) lcUpperLine = UPPER(lcLine) lcFoundVar = "" DO CASE CASE llInMethod IF "ENDPROC" == LEFT(lcUpperLine, 7) OR "ENDFUNC" == LEFT(lcUpperLine, 7) Then llInMethod = .F. ENDIF * Loop-- we don't do anything with method code... LOOP CASE "FUNCTION " == LEFT(lcUpperLine, 9) llInMethod = .T. LOCAL liStartParen liStartParen = AT("(", lcLine) lcFoundVar = SUBSTR(lcLine, 10, liStartParen-9) CASE "PROTECTED FUNCTION " == LEFT(lcUpperLine, 19) llInMethod = .T. LOCAL liStartParen liStartParen = AT("(", lcUpperLine) LOCAL liStartParen liStartParen = AT("(", lcLine) lcFoundVar = SUBSTR(lcLine, 20, liStartParen-19) CASE "PROCEDURE " == LEFT(lcUpperLine, 10) llInMethod = .T. lcFoundVar = SUBSTR(lcLine, 11) CASE "=" $ lcUpperLine AND NOT "WITH" $ lcUpperLine * We'll define a property line as one that includes an "=" in it, * because I believe they all need to... Exclude any 'WITH's * from AddObjects... LOCAL laPair[1] ALINES(laPair, WSTRIM(lcLine), "=") lcFoundVar = WSTRIM(laPair[1]) ENDCASE IF !EMPTY(lcFoundVar) AND UPPER(cVar) == SUBSTR(UPPER(lcFoundVar), 1, LEN(cVar)) Then * Add Match to list iMatches = iMatches + 1 DIMENSION aMatches[iMatches] aMatches[iMatches] = lcFoundVar ENDIF NEXT ENDIF CASE !EMPTY(cVar) AND !("." $ cVar) LOCAL cProgText, iLastLine, aProgText[1], iLine, cLine cProgText = _EdGetStr(hwndCode, 0, iPos) && get all code from the beginning to our current cursor position, as we care about nothing more. IF LEN(cProgText) > 0 Then iLastLine = ALINES(aProgText, cProgText) FOR iLine = iLastLine TO 1 STEP -1 LOCAL cVariables cVariables = "" cLine = WSTRIM(aProgText[iLine]) DO CASE CASE "LOCAL " == UPPER(LEFT(cLine, 6)) cVariables = WSTRIM(SUBSTR(cLine, 7)) CASE "PROCEDURE " == UPPER(LEFT(cLine, 10)) EXIT CASE "FUNCTION " == UPPER(LEFT(cLine, 9)) OR "PROTECTED FUNCTION " == UPPER(LEFT(cLine, 19)) LOCAL liStartParen, liEndParen liStartParen = AT("(", cLine) liEndParen = AT(")", cLine) IF liStartParen > 0 AND liEndParen > liStartParen Then cVariables = WSTRIM(SUBSTR(cLine, liStartParen+1, liEndParen-liStartParen-1)) iLine = 0 && This forces the loop to exit after it does the dirty work... ELSE EXIT ENDIF CASE "LPARAMETERS " == UPPER(LEFT(cLine, 12)) cVariables = WSTRIM(SUBSTR(cLine, 13)) CASE "PARAMETERS " == UPPER(LEFT(cLine, 11)) cVariables = WSTRIM(SUBSTR(cLine, 12)) CASE "PRIVATE " == UPPER(LEFT(cLine, 8)) cVariables = WSTRIM(SUBSTR(cLine, 9)) CASE "PUBLIC " == UPPER(LEFT(cLine, 7)) cVariables = WSTRIM(SUBSTR(cLine, 8)) CASE "DIMENSION " == UPPER(LEFT(cLine, 10)) cVariables = WSTRIM(SUBSTR(cLine, 11)) ENDCASE * Let's handle multi-line definitions... LOCAL iOffset iOffset = 0 DO WHILE ";" == RIGHT(cVariables, 1) iOffset = iOffset + 1 cVariables = LEFT(cVariables, LEN(cVariables)-1) + WSTRIM(aProgText[iLine+iOffset]) ENDDO LOCAL aVars[1], y FOR y = 1 TO ASPLIT(@aVars, cVariables, ",") LOCAL cDeclaredVar cDeclaredVar = WSTRIM(aVars[y]) IF " " $ cDeclaredVar Then && Don't include anything beyond the space; Strong-typing comes to mind cDeclaredVar = LEFT(cDeclaredVar, AT(" ", cDeclaredVar)-1) ENDIF IF CHR(9) $ cDeclaredVar Then && Don't include anything beyond the tab cDeclaredVar = LEFT(cDeclaredVar, AT(CHR(9), cDeclaredVar)-1) ENDIF IF "[" $ cDeclaredVar Then && Don't include anything beyond the array subscript; We could set this up to include the array subscript, as it will be used most places... cDeclaredVar = LEFT(cDeclaredVar, AT("[", cDeclaredVar)-1) ENDIF IF "(" $ cDeclaredVar Then && Don't include anything beyond the array subscript(some people use these parentheses for arrays!!!) cDeclaredVar = LEFT(cDeclaredVar, AT("(", cDeclaredVar)-1) ENDIF IF UPPER(cVar) == SUBSTR(UPPER(cDeclaredVar), 1, LEN(cVar)) Then * Add Match to list iMatches = iMatches + 1 DIMENSION aMatches[iMatches] aMatches[iMatches] = cDeclaredVar ENDIF NEXT NEXT ENDIF ENDCASE IF iMatches > 0 Then && we have at least one match! LOCAL cNewVar cNewVar = GetBestMatch(@aMatches, cVar) IF UPPER(cVar) == UPPER(cNewVar) Then IF iMatches > 1 Then LOCAL cChoice, nRow, nCol, iMatch, cOnSelect cChoice = "" nRow = 0 nCol = 0 EditPos(@nRow, @nCol, @laEnv) DEFINE POPUP shortcut shortcut && RELATIVE FROM MROW(),MCOL() FOR iMatch = 1 TO iMatches DEFINE BAR iMatch OF shortcut PROMPT aMatches[iMatch] cOnSelect = [ON SELECTION BAR iMatch OF shortcut cChoice = "] + aMatches[iMatch] + ["] &cOnSelect ENDFOR ACTIVATE POPUP shortcut AT nRow, nCol cNewVar = cChoice ENDIF ENDIF ChangeText(@hwndCode, laEnv[17], cVar, cNewVar) ELSE * If there's no match, BEEP!!! ??CHR(7) ENDIF ENDIF ENDIF RETURN .T. FUNCTION GetBestMatch LPARAMETERS taMatches, cVar LOCAL cMatch, iMatches, i cMatch = "" iMatches = ALEN(taMatches) ASORT(taMatches) FOR i = 1 TO LEN(taMatches[1]) && size of shortest var LOCAL cChar, cOldChar, j cOldChar = "" FOR j = 1 TO iMatches cChar = SUBSTR(taMatches[j], i, 1) IF NOT EMPTY(cOldChar) IF UPPER(cChar) != UPPER(cOldChar) Then RETURN cMatch ENDIF ENDIF cOldChar = cChar NEXT cMatch = cMatch + cChar NEXT RETURN cMatch FUNCTION WSTRIM LPARAMETERS tcText LOCAL lcText lcText = ALLTRIM(CHRTRAN(tcText, CHR(13) + CHR(10), "")) DO WHILE CHR(9) == LEFT(lcText, 1) OR " " == LEFT(lcText, 1) lcText = SUBSTR(lcText, 2) ENDDO DO WHILE CHR(9) == RIGHT(lcText, 1) OR " " == RIGHT(lcText, 1) lcText = LEFT(lcText, LEN(lcText)-1) ENDDO RETURN lcText FUNCTION ASPLIT LPARAMETERS taArray, tcText, tcSplitChar LOCAL iElements, x, iOld, iNext iElements = OCCURS(tcSplitChar, tcText)+1 DIMENSION taArray[iElements] iOld = 1 FOR x = 1 TO iElements iNext = AT(tcSplitChar, tcText, x) IF iNext = 0 THEN taArray[x] = SUBSTR(tcText, iOld) ELSE taArray[x] = SUBSTR(tcText, iOld, iNext-iOld) ENDIF iOld = iNext+1 NEXT RETURN iElements FUNCTION ChangeText LPARAMETERS thWnd, tiPos, tcOldVar, tcNewVar _EdSelect(thWnd, tiPos-LEN(tcOldVar), tiPos) _EdDelete(thWnd) _EdInsert(thWnd, tcNewVar, LEN(tcNewVar)) RETURN .T. FUNCTION GetVariable LPARAMETERS thWnd, tiPos LOCAL cVar, cChar, iCnt cVar = "" iCnt = tiPos - 1 cChar = _EdGetChar(thWnd, iCnt) DO WHILE cChar $ VARIABLE_CHARS cVar = cChar + cVar iCnt = iCnt - 1 cChar = _EdGetChar(thWnd, iCnt) ENDDO RETURN cVar FUNCTION EditPos (nRow, nCol, taEnv) LOCAL cPoint, cLeft, cTop, nLeft, nTop #DEFINE EDENV_KIND 25 #DEFINE EDSNIP 10 nRow = 0 nCol = 0 DECLARE Long GetCaretPos in Win32API String@ DECLARE memcpy IN msvcrt AS memcpy_s2l Long@, String@, Long ** Get the caret point cPoint = REPLICATE(CHR(0), 8) GetCaretPos(@cPoint) ** Convert the point to top and left cLeft = SUBSTR(cPoint, 1, 4) cTop = SUBSTR(cPoint, 5, 4) nLeft = 0 nTop = 0 memcpy_s2l(@nLeft, @cLeft, 4) memcpy_s2l(@nTop, @cTop, 4) ** Add window border dimensions nTop = nTop + SYSMETRIC(4)+SYSMETRIC(9) nLeft = nLeft + SYSMETRIC(3) ** If this is a snippet screen add more space for the combos IF INLIST(taEnv[EDENV_KIND], EDSNIP) nTop = nTop + 38 ENDIF ** Get edit window corner nRow = WLROW(WONTOP()) nCol = WLCOL(WONTOP()) ** Add caret position ** (add 1 to the height for the height of the caret) *! ToDo: 1 is close, but we need to calculate the caret height *! based on the current font dimensions nRow = nRow + (nTop/FONTMETRIC(1)) + 1 nCol = nCol + (nLeft/FONTMETRIC(6)) RETURN .T.