simonsteele / pn

Programmer's Notepad
374 stars 115 forks source link

Feature Request: CTRL-K feature from Netbeans IDE #191

Open Thomassso opened 5 years ago

Thomassso commented 5 years ago

Hi!

I just finished my new PyPN script to implement CTRL-K feature from NetBeans IDE. It searches for similar words backward and pastes it to the current position. This helps you to reduce typing because you will need to type only few chars (mostly 2) and then hitting CTRL-K will search for words beginning with the 2 chars. If you hit CTRL-K multiple times then it will put there another previous matching word. So you can quickly get pasted any previously used long variable or method name beginning with chars you typed.

Please publish it on PN2's web page https://www.pnotepad.org/docs/list_of_user-submitted_pypn_scripts/ since http://scriptshare.rocketmonkeys.com/ is not available anymore for script sharing. A better option is to put this script into the installation package and assign CTRL-K (and CTRL-SHIFT-K) shortcuts by default to this script.

Thanks!

######################################################################################
## previousMatchingWord.py -- PyPN utility script to insert previous matching word
##  tested on Python 3.4.
## Author: Tamás Bene, Slovakia (tomasPUTDOTHEREbeneMAILATatlasPUTDOTHEREcz)
##
## This utility will replace the word at the cursor with previous matching word.
##  If invoked multiple times then it will put every matching word one by one.
##  Works similar as CTRL-K and CTRL-SHIFT-K in Netbeans IDE
##
##  Example: I declare the variable bicycleAverageDailyMileage then later
##   in the code I will type "bi" and then launching this script the
##   word "bi" will be replaced by "bicycleAverageDailyMileage". If not then
##   I will launch the script again and again until there will be
##   "bicycleAverageDailyMileage".
##
##  Example2: There is a variable with dot notation in Java
##   code "sportActivity.bicycle.dailyMileage". When I type "sportActivity."
##   later in the code (notice the dot at the end) and launching this script
##   will replace it with "sportActivity." will be replaced by "sportActivity.bicycle"
##   or any other variable, class or method of sportActivity previously already
##   used in the code. If you type something after the dot, like "sportActivity.bi"
##   then the script will search for any word beginning with
##   "bi" (e.g. bicycle, bicubic, binocular, etc.). It will not search for
##   "sportActivity.bi". This gives you freedom to get there any replacement word.
##
## You can assign shortcut (e.g. [CTRL]-K ) for this script in menu "Tools / Options"
##  then in Keyboard options find group "Scripts.Replace" and select
##  script "Previous Matching Word".
##  Also you can assign [CTRL]=[SHIFT]-K to script "Next Matching Word".
##
## Warning: PN2 is a 32 bit software therefore you need to install 32 bit version
##  of Python even on 64 bit Windows.
######################################################################################

import pn
import scintilla
from pypn.decorators import script

def log(text, loggingIsOn) :
  """ Prints text to the PN2's output window only if debugging is on"""
  if loggingIsOn :
    pn.AddOutput("\n" + text)

@script("Previous Matching Word", "Replace")
def replaceWordAtCursorWithPrevMatchingWord() :
  """ Searches for previous matching word and inserts it (by Tomas Bene) """

  # Enable or disable debug log printing to Output window of PN2 (F8 to show/hide Output Window)
  debugLogIsOn = False

  # Scintilla SearchFlag constants. Sum them to get your SearchFlag.
  # # Source: https://sourceforge.net/p/scintilla/scite/ci/default/tree/src/IFaceTable.cxx
  # # # Lines: 2105 - 2111
  SCFIND_WHOLEWORD = 0x2
  SCFIND_MATCHCASE = 0x4
  SCFIND_WORDSTART = 0x00100000
  SCFIND_REGEXP = 0x00200000
  SCFIND_POSIX = 0x00400000
  SCFIND_CXX11REGEX = 0x00800000
  # # # # # # # # # # #

  editor = scintilla.Scintilla(pn.CurrentDoc())
  lastSearchedTextStartPos = editor.GetPropertyInt("userScript.previousMatchingWord.lastSearchedTextStartPos", -1)
  lastSearchedTextEndPos = editor.GetPropertyInt("userScript.previousMatchingWord.lastSearchedTextEndPos", -1)
  lastSearchFoundPos = editor.GetPropertyInt("userScript.previousMatchingWord.lastSearchFoundPos", -1)
  searchingWordWithDotNotation = editor.GetPropertyInt("userScript.previousMatchingWord.searchingWordWithDotNotation", 0)

  if editor.SelectionStart == editor.SelectionEnd :
    # Get word at cursor
    cursorPosition = editor.CurrentPos
    wordStart = editor.WordStartPosition(cursorPosition, True)
    wordEnd = editor.WordEndPosition(cursorPosition, True)
    wordAtCursor = editor.GetTextRange(wordStart, wordEnd)

    # If the script previously detected a word with Dot Notation then find the beginning of this word (including all dots)
    if ( searchingWordWithDotNotation == 1 ) :
      if ( wordStart > lastSearchedTextStartPos ) and ( wordStart == lastSearchedTextEndPos):
        wordStart = lastSearchedTextStartPos
      else :
        # Clear the information about the last search because the cursor left the last replacement position
        editor.SetProperty("userScript.previousMatchingWord.lastSearchedTextStartPos", "")
        editor.SetProperty("userScript.previousMatchingWord.lastSearchFoundPos", "")
        editor.SetProperty("userScript.previousMatchingWord.searchingWordWithDotNotation", "")

    if wordStart < wordEnd :
      findWhatStart = wordStart
      findWhatEnd = wordEnd
      # Detects if the script is launched more times on the same word (under the cursor).
      # When launching this script second (or more) times then it will not search for the word
      #  under the cursor (because it is an already replaced word) but for the previously searched word
      #  which is remembered in scintilla's document properties.
      if ( lastSearchedTextStartPos != -1 ) and ( wordStart == lastSearchedTextStartPos ) and ( wordEnd > lastSearchedTextEndPos ) :
        findWhatEnd = lastSearchedTextEndPos
      # Launching second (and more) times the script begins the searching from the position where finished the search last time
      if ( lastSearchFoundPos == -1 ) or ( wordStart != lastSearchedTextStartPos ) :
        findFromPos = wordStart
      else :
        findFromPos = lastSearchFoundPos

      findWhatText = editor.GetTextRange(findWhatStart, findWhatEnd)
      if findWhatText != "" :
        foundPos = editor.FindText(findFromPos, 0, findWhatText, SCFIND_WORDSTART)
        # FindText returns boolean value if there is no match otherwise a tuple.
        if type(foundPos) is tuple :
          foundText = editor.GetTextRange(foundPos[0], editor.WordEndPosition(foundPos[1], True))
          editor.TargetStart = wordStart
          editor.TargetEnd = wordEnd
          editor.ReplaceTarget(len(foundText), foundText)
          editor.GotoPos(wordStart + len(foundText))
          editor.SetProperty("userScript.previousMatchingWord.lastSearchedTextStartPos", str(findWhatStart))
          editor.SetProperty("userScript.previousMatchingWord.lastSearchedTextEndPos", str(findWhatEnd))
          editor.SetProperty("userScript.previousMatchingWord.lastSearchFoundPos", str(foundPos[0]))
          log("Searched: '" + findWhatText + "'", debugLogIsOn)
          log("Found: '" + foundText + "'", debugLogIsOn)
          log("Search started at position: " + str(findFromPos), debugLogIsOn)
          log("Match found at position: " + str(foundPos[0]), debugLogIsOn)
        else :
          editor.SetProperty("userScript.previousMatchingWord.lastSearchFoundPos", "")
          log("Searched: '" + findWhatText + "'", debugLogIsOn)
          log("Search started at position: " + str(findFromPos), debugLogIsOn)
          if lastSearchFoundPos == -1 :
            log("Can't find any previous occurence of the word.", debugLogIsOn)
          else :
            log("Can't find other previous occurence of the word. Last search position will be reset.", debugLogIsOn)
      else :
        editor.SetProperty("userScript.previousMatchingWord.lastSearchedTextStartPos", "")
        editor.SetProperty("userScript.previousMatchingWord.lastSearchFoundPos", "")
        editor.SetProperty("userScript.previousMatchingWord.searchingWordWithDotNotation", "")
        log("There is no word under the cursor.", debugLogIsOn)
    else :
      # There is no word at the cursor.
      # Maybe there is a dot ended word and we will remember it in order to process strings with Dot Notation.
      if editor.GetCharAt(wordStart - 1) == ord(".") :
        while editor.GetCharAt(wordStart - 1) == ord(".") :
          wordStart = editor.WordStartPosition(wordStart - 1, True)
        editor.SetProperty("userScript.previousMatchingWord.lastSearchedTextStartPos", str(wordStart))
        editor.SetProperty("userScript.previousMatchingWord.lastSearchedTextEndPos", str(wordEnd))
        editor.SetProperty("userScript.previousMatchingWord.lastSearchFoundPos", str(wordStart))
        editor.SetProperty("userScript.previousMatchingWord.searchingWordWithDotNotation", "1")
        log("Dot Notation found.", debugLogIsOn)
        replaceWordAtCursorWithPrevMatchingWord()
      else :
        # Clear the information about the last search because there is no word at the cursor which means
        #  the cursor left the last replacement position
        editor.SetProperty("userScript.previousMatchingWord.lastSearchedTextStartPos", "")
        editor.SetProperty("userScript.previousMatchingWord.lastSearchFoundPos", "")
        editor.SetProperty("userScript.previousMatchingWord.searchingWordWithDotNotation", "")
        log("There is no word under the cursor.", debugLogIsOn)
  else :
    log("Can't work with selection. Previous Matching Word uses word at the cursor.", debugLogIsOn)
  if debugLogIsOn :
    editor.GrabFocus()

@script("Next Matching Word", "Replace")
def replaceWordAtCursorWithNextMatchingWord() :
  """ Searches for previous matching word and inserts it (by Tomas Bene) """

  # Enable or disable debug log printing to Output window of PN2 (F8 to show/hide Output Window)
  debugLogIsOn = False

  # Scintilla SearchFlag constants. Sum them to get your SearchFlag.
  # # Source: https://sourceforge.net/p/scintilla/scite/ci/default/tree/src/IFaceTable.cxx
  # # # Lines: 2105 - 2111
  SCFIND_WHOLEWORD = 0x2
  SCFIND_MATCHCASE = 0x4
  SCFIND_WORDSTART = 0x00100000
  SCFIND_REGEXP = 0x00200000
  SCFIND_POSIX = 0x00400000
  SCFIND_CXX11REGEX = 0x00800000
  # # # # # # # # # # #

  editor = scintilla.Scintilla(pn.CurrentDoc())
  lastSearchedTextStartPos = editor.GetPropertyInt("userScript.previousMatchingWord.lastSearchedTextStartPos", -1)
  lastSearchedTextEndPos = editor.GetPropertyInt("userScript.previousMatchingWord.lastSearchedTextEndPos", -1)
  lastSearchFoundPos = editor.GetPropertyInt("userScript.previousMatchingWord.lastSearchFoundPos", -1)
  searchingWordWithDotNotation = editor.GetPropertyInt("userScript.previousMatchingWord.searchingWordWithDotNotation", 0)

  if editor.SelectionStart == editor.SelectionEnd :
    # Get word at cursor
    cursorPosition = editor.CurrentPos
    wordStart = editor.WordStartPosition(cursorPosition, True)
    wordEnd = editor.WordEndPosition(cursorPosition, True)
    wordAtCursor = editor.GetTextRange(wordStart, wordEnd)

    # If the script previously detected a word with Dot Notation then find the beginning of this word (including all dots)
    if ( searchingWordWithDotNotation == 1 ) :
      if ( wordStart > lastSearchedTextStartPos ) and ( wordStart == lastSearchedTextEndPos):
        wordStart = lastSearchedTextStartPos
      else :
        # Clear the information about the last search because the cursor left the last replacement position
        editor.SetProperty("userScript.previousMatchingWord.lastSearchedTextStartPos", "")
        editor.SetProperty("userScript.previousMatchingWord.lastSearchFoundPos", "")
        editor.SetProperty("userScript.previousMatchingWord.searchingWordWithDotNotation", "")

    if wordStart < wordEnd :
      findWhatStart = wordStart
      findWhatEnd = wordEnd
      # Detects if the script is launched more times on the same word (under the cursor).
      # When launching this script second (or more) times then it will not search for the word
      #  under the cursor (because it is an already replaced word) but for the previously searched word
      #  which is remembered in scintilla's document properties.
      if ( lastSearchedTextStartPos != -1 ) and ( wordStart == lastSearchedTextStartPos ) and ( wordEnd > lastSearchedTextEndPos ) :
        findWhatEnd = lastSearchedTextEndPos
      # Launching second (and more) times the script begins the searching from the position where finished the search last time
      if ( lastSearchFoundPos == -1 ) or ( wordStart != lastSearchedTextStartPos ) :
        findFromPos = wordStart
      else :
        findFromPos = lastSearchFoundPos

      findWhatText = editor.GetTextRange(findWhatStart, findWhatEnd)
      if findWhatText != "" :
        foundPos = editor.FindText(findFromPos+len(findWhatText), editor.Length, findWhatText, SCFIND_WORDSTART)
        # FindText returns boolean value if there is no match otherwise a tuple.
        if type(foundPos) is tuple :
          foundText = editor.GetTextRange(foundPos[0], editor.WordEndPosition(foundPos[1], True))
          editor.TargetStart = wordStart
          editor.TargetEnd = wordEnd
          editor.ReplaceTarget(len(foundText), foundText)
          editor.GotoPos(wordStart + len(foundText))
          editor.SetProperty("userScript.previousMatchingWord.lastSearchedTextStartPos", str(findWhatStart))
          editor.SetProperty("userScript.previousMatchingWord.lastSearchedTextEndPos", str(findWhatEnd))
          editor.SetProperty("userScript.previousMatchingWord.lastSearchFoundPos", str(foundPos[0]))
          log("Searched: '" + findWhatText + "'", debugLogIsOn)
          log("Found: '" + foundText + "'", debugLogIsOn)
          log("Search started at position: " + str(findFromPos), debugLogIsOn)
          log("Match found at position: " + str(foundPos[0]), debugLogIsOn)
        else :
          editor.SetProperty("userScript.previousMatchingWord.lastSearchFoundPos", "")
          log("Searched: '" + findWhatText + "'", debugLogIsOn)
          log("Search started at position: " + str(findFromPos), debugLogIsOn)
          if lastSearchFoundPos == -1 :
            log("Can't find any next occurence of the word.", debugLogIsOn)
          else :
            log("Can't find other next occurence of the word. Last search position will be reset.", debugLogIsOn)
      else :
        editor.SetProperty("userScript.previousMatchingWord.lastSearchedTextStartPos", "")
        editor.SetProperty("userScript.previousMatchingWord.lastSearchFoundPos", "")
        editor.SetProperty("userScript.previousMatchingWord.searchingWordWithDotNotation", "")
        log("There is no word under the cursor.", debugLogIsOn)
    else :
      # There is no word at the cursor.
      # Maybe there is a dot ended word and we will remember it in order to process strings with Dot Notation.
      if editor.GetCharAt(wordStart - 1) == ord(".") :
        while editor.GetCharAt(wordStart - 1) == ord(".") :
          wordStart = editor.WordStartPosition(wordStart - 1, True)
        editor.SetProperty("userScript.previousMatchingWord.lastSearchedTextStartPos", str(wordStart))
        editor.SetProperty("userScript.previousMatchingWord.lastSearchedTextEndPos", str(wordEnd))
        editor.SetProperty("userScript.previousMatchingWord.lastSearchFoundPos", str(wordStart))
        editor.SetProperty("userScript.previousMatchingWord.searchingWordWithDotNotation", "1")
        log("Dot Notation found.", debugLogIsOn)
        replaceWordAtCursorWithNextMatchingWord()
      else :
        # Clear the information about the last search because there is no word at the cursor which means
        #  the cursor left the last replacement position
        editor.SetProperty("userScript.previousMatchingWord.lastSearchedTextStartPos", "")
        editor.SetProperty("userScript.previousMatchingWord.lastSearchFoundPos", "")
        editor.SetProperty("userScript.previousMatchingWord.searchingWordWithDotNotation", "")
        log("There is no word under the cursor.", debugLogIsOn)
  else :
    log("Can't work with selection. Next Matching Word uses word at the cursor.", debugLogIsOn)
  if debugLogIsOn :
    editor.GrabFocus()