Community
    • Login

    Calltip on mouse-over from UDL xml file (Python)

    Scheduled Pinned Locked Moved Help wanted · · · – – – · · ·
    21 Posts 5 Posters 1.7k Views
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • Khundian TwitchK
      Khundian Twitch @Alan Kilborn
      last edited by

      @Alan-Kilborn Thanks for the reply
      I wouldn’t know to be honest. I haven’t used Python that much before. Its not only line breaks though, at the moment it shows the text in brackets too. Picture below also shows the dictionary created with ElementTree not respecting the order of the XML.

      2024-01-19 13_34_12-Window.png

      PeterJonesP 1 Reply Last reply Reply Quote 0
      • PeterJonesP
        PeterJones @Khundian Twitch
        last edited by

        @Khundian-Twitch ,

        You shouldn’t be using the default Python language stringification of the data structure element as the popup, if you want the string to be user-friendly. You should be extracting the elements from that sub-object that are useful to you, in the order you want them to appear, using one of Python’s string-formatting techniques. (That’s nothing specific to Notepad++. Any time you want to take data from an object and present it as a string in Python, you need to format the string to make it look like you want – and if you need help with that, it’s not really a question for here, because that’s generic programming help, not Notepad++ specific.)

        Khundian TwitchK 1 Reply Last reply Reply Quote 2
        • Khundian TwitchK
          Khundian Twitch @PeterJones
          last edited by

          @PeterJones Thanks for the reply and the advice
          I understand that my questions after my initial post are not directly related to Notepad++ anymore. But these nudges in the right direction I got from you is all that I needed,

          I looked into string formatting, and got a step further. Currently it shows only the description of a keyword as calltip, and it only works if there is only 1 Overload entry. I’m going to check how to make it work with multiple hint definitions and the up and down arrows.

          I included a picture and the test.xml as an example in case someone wants to use the script in its current state. All text will be wrapped at max 90 characters, and as stated in the Notepad++ documentation for line breaks use

          

          

          2024-01-30 23_09_32-Window.png

          <NotepadPlus>
          	<AutoComplete language="">
          		<Environment ignoreCase="yes"/>
          		<KeyWord name="ActivateAnim" func="yes">
          			<Overload retVal="" descr='
          &#x0a;------------------------------------------------------------------------------------------
          &#x0a;Plays the specified anim at the specified priority and weight, with optional parameters.
          All arguments except the sequence path are optional.
          This is a low-level animation function that must be used with care.
          Use BlendToAnim to deactivate or transition the animation to a different one.
          &#x0a;------
          &#x0a;Syntax
          &#x0a;------
          &#x0a;actor.ActivateAnim anim sequence path:string
          &#x0a;bIsFirstPerson:0/1
          &#x0a;iPriority:integer
          &#x0a;bStartOver:0/1
          &#x0a;fWeight:float
          &#x0a;fEaseInTime:float
          &#x0a;time sync sequence path:string
          &#x0a;-------
          &#x0a;Example
          &#x0a;-------
          &#x0a;ActivateAnim "Characters\Recoil.kf"
          &#x0a;------------------------------------------------------------------------------------------
          			'>
          				<Param name=""/>
          			</Overload>
          		</KeyWord>
          	</AutoComplete>
          </NotepadPlus>
          
          
          # Calltip on mouse-over with ElementTree
          from __future__ import print_function
          
          # Import the required modules
          import xml.etree.ElementTree as ET
          from collections import defaultdict
          from pprint import pprint
          import textwrap
          
          
          # Import and parse the data from file
          tree = ET.parse("test.xml")
          root = tree.getroot()
          
          # Create empty dictionary
          data_dict = {}
          
          
          # Imoort the XML data into a Python dictionary
          def etree_to_dict(t):
              d = {t.tag: {} if t.attrib else None}
              children = list(t)
              if children:
                  dd = defaultdict(list)
                  for dc in map(etree_to_dict, children):
                      for k, v in dc.items():
                          dd[k].append(v)
                  d = {t.tag: {k: v[0] if len(v) == 1 else v for k, v in dd.items()}}
              if t.attrib:
                  d[t.tag].update(("" + k, v) for k, v in t.attrib.items())
              if t.text:
                  text = t.text.strip()
                  if children or t.attrib:
                      if text:
                          d[t.tag]["#text"] = text
                  else:
                      d[t.tag] = text
              return d
          
          
          data_dict = etree_to_dict(root)
          
          # Print dictionary to console
          # pprint(data_dict, indent=4)
          
          # Clear callbacks
          editor.clearCallbacks()
          
          # Set dwell time
          editor.setMouseDwellTime(500)
          
          
          # Cancel calltip
          def dwell_end(args):
              editor.callTipCancel()
          
          
          # Get word under mouse
          def dwell_start(args):
              pos = args["position"]
              dwell_word = editor.getTextRange(
                  editor.wordStartPosition(pos, True), editor.wordEndPosition(pos, True)
              )
              # Print word under mouse to console
              # print("Mouse over:", dwell_word)
          
              # Search for the the dwell_word in the dictionary
              for keyword in data_dict["NotepadPlus"]["AutoComplete"]["KeyWord"]:
                  if keyword.get("name") == dwell_word:
                      nl = "\n"
                      print(dwell_word, "found in dictionary")
                      if keyword.get("func") == ("yes"):
                          # Get name of keyword from dictionary
                          namevalue = keyword.get("name")
                          # Get if keyword is a function, yes/no
                          funcvalue = keyword.get("func")
                          # Get all overload subs
                          overloadvalues = keyword.get("Overload")
                          # Get description of keyword
                          descrvalue = overloadvalues.get("descr")
                          # Wrap the description text for calltip
                          descrvaluewrap = nl.join(
                              [
                                  nl.join(
                                      textwrap.wrap(
                                          line,
                                          90,
                                          break_long_words=False,
                                          replace_whitespace=False,
                                      )
                                  )
                                  for line in descrvalue.splitlines()
                                  if line.strip() != ""
                              ]
                          )
          
                          # Print stuff to console
                          # print(namevalue)
                          # print(funcvalue)
                          # pprint(overloadvalues, indent=2)
                          # print(descrvalue)
                          # print(descrvaluewrap)
          
                          # Show the calltip
                          editor.callTipShow(editor.getCurrentPos(), descrvaluewrap)
          
          
          # Open console and show install message
          console.show()
          print("Calltip on mouse-over with ElementTree installed.")
          
          # Scintilla callback notifications
          show = editor.callback(dwell_start, [SCINTILLANOTIFICATION.DWELLSTART])
          cancel = editor.callback(dwell_end, [SCINTILLANOTIFICATION.DWELLEND])
          
          
          1 Reply Last reply Reply Quote 2
          • Khundian TwitchK
            Khundian Twitch
            last edited by Khundian Twitch

            I’ve decided to go with only 1 “Overload” entry per keyword for the time being. I couldn’t find any clear documentation or examples on how to implement them, if anyone has any experience with this… I’m all ears. Unless its to add multiple overloads, this will be the “final” version of the script. A bit funny that it turned out to be “on character” and not “mouse-over”… :)

            Something weird I noticed while testing with the multiple overloads, when the calltip is showing and I use the “alt up” and “alt down” shortcuts it crashes Notepad++ to desktop. Even with the script below that only has 1 “Overload” entry it CTD, clicking on the calltip doesn’t crash btw. I have no idea why it does this, I disabled the parameter hint shortcuts, and recommend it to anyone who wants to use the script below until the cause is found.

            I changed the script to work on a “space” instead of mouse-over, but it can easily be changed to any character you want. I added explanations in the comments on how to edit the script to work with any User Defined Language, and how to change the character that triggers the calltip. Formatted the calltip some more and cleaned the script up, if anyone needs help to get it running… don’t hesistate to ask.

            2024-02-01 22_19_30-Window.png

            <NotepadPlus>
            	<AutoComplete language="">
            		<Environment ignoreCase="yes"/>
            		<KeyWord name="AddAmmoEffect" func="yes">
            			<Overload retVal="(successful:0/1)" descr='
            &#x0a;Adds an ammo effect to an ammo type.
            &#x0a;------
            &#x0a;Syntax
            &#x0a;------
            &#x0a;(successful:0/1) AddAmmoEffect ammo:ref ammoEffect:ref
            &#x0a;-------
            &#x0a;Example
            &#x0a;-------
            &#x0a;AddAmmoEffect Ammo12Ga AmmoEffectBeanBagFatigue 
            			'>
            				<Param name="Form:AnyForm"/>
            			</Overload>
            		</KeyWord>
            	</AutoComplete>
            </NotepadPlus>
            
            # Calltip on any character
            from __future__ import print_function
            
            # Import the required modules
            import xml.etree.ElementTree as ET
            from collections import defaultdict
            import textwrap
            
            # Import and parse the data from file
            tree = ET.parse("PATH/TO/YOUR/AUTOCOMPLETE.XML")
            root = tree.getroot()
            
            # Create empty dictionary
            data_dict = {}
            
            
            # Imoort the XML data into a Python dictionary
            def etree_to_dict(t):
                d = {t.tag: {} if t.attrib else None}
                children = list(t)
                if children:
                    dd = defaultdict(list)
                    for dc in map(etree_to_dict, children):
                        for k, v in dc.items():
                            dd[k].append(v)
                    d = {t.tag: {k: v[0] if len(v) == 1 else v for k, v in dd.items()}}
                if t.attrib:
                    d[t.tag].update(("" + k, v) for k, v in t.attrib.items())
                if t.text:
                    text = t.text.strip()
                    if children or t.attrib:
                        if text:
                            d[t.tag]["#text"] = text
                    else:
                        d[t.tag] = text
                return d
            
            
            data_dict = etree_to_dict(root)
            
            
            # Padding for the calltip
            pad = "----------------------------------------------------------------------"
            
            
            # Clear callbacks
            editor.clearCallbacks()
            
            
            # Get word before the chosen character
            def on_char_add(args):
                # ---------------------------------------------------------------
                # The line below makes sure that the code below will only run
                # when a document is active with your chosen language.
                # Change YOURLANGUAGENAME to the exact name of your User Defined
                # Language.
                # ---------------------------------------------------------------
                if notepad.getLanguageName(LANGTYPE.USER) == "udf - YOURLANGUAGENAME":
                    # -----------------------------------------------------------
                    # Uncomment the line below, and run the script.
                    # When typing text in a file with the chosen language above,
                    # the "ch" number will show in the console.
                    # Use that number to change the added_char value below,
                    # this will change the character that opens the calltip.
                    # -----------------------------------------------------------
                    # print(args)
                    added_char = args["ch"]
                    if (
                        added_char == 32
                    ):  # Number 32 is the "space" character, change to whatever you want.
                        pos = editor.getCurrentPos() - 1
                        search_word = editor.getTextRange(
                            editor.wordStartPosition(pos, True), editor.wordEndPosition(pos, True)
                        )
            
                        # Search for the the search_word information in the dictionary
                        for keyword in data_dict["NotepadPlus"]["AutoComplete"]["KeyWord"]:
                            if keyword.get("name") == search_word:
                                nl = "\n"
                                name_value = keyword.get("name")
                                overload_values = keyword.get("Overload")
                                retval_value = overload_values.get("retVal")
                                descr_value = overload_values.get("descr")
                                param_value = keyword.get("Overload")["Param"]["name"]
                                retval_text = "RetVal: " + retval_value
                                param_text = "Param: " + param_value
            
                                # Create calltip from all retrieved text
                                calltip = (
                                    pad
                                    + nl
                                    + name_value
                                    + nl
                                    + pad
                                    + nl
                                    + retval_text
                                    + nl
                                    + pad
                                    + nl
                                    + descr_value
                                    + nl
                                    + pad
                                    + nl
                                    + param_text
                                    + nl
                                    + pad
                                )
            
                                # Wrap the text for calltip
                                calltip_wrap = nl.join(
                                    [
                                        nl.join(
                                            textwrap.wrap(
                                                line,
                                                70,
                                                break_long_words=False,
                                                replace_whitespace=False,
                                            )
                                        )
                                        for line in calltip.splitlines()
                                        if line.strip() != ""
                                    ]
                                )
            
                                # Show the calltip
                                editor.callTipShow(editor.getCurrentPos(), calltip_wrap)
            
            
            # Open console and show install message
            console.show()
            print("Calltip on any character installed, its safe to close this console.")
            
            
            # Scintilla callback notifications
            editor.callback(on_char_add, [SCINTILLANOTIFICATION.CHARADDED])
            
            
            1 Reply Last reply Reply Quote 1
            • Khundian TwitchK
              Khundian Twitch
              last edited by

              I had some free time and had another go at multiple overloads, but there seems to be a bug with the down arrow in the tooltip / and the alt+down shortcut combo when using PythonScript. Obviously there is nothing wrong with the standard implementation of this feature, only when creating a custom tooltip with PythonScript does this CTD happen.

              I did a small test with the SCINTILLANOTIFICATION.CALLTIPCLICK and this was the result, position 0 is clicked anywhere but the arrows, position 1 is clicked on the up arrow and position 2 should be clicked on the down arrow… but this results in Notepad++ crashing to desktop.

              2024-02-02 03_59_55-Window.png

              Lycan ThropeL mpheathM 2 Replies Last reply Reply Quote 0
              • Lycan ThropeL
                Lycan Thrope @Khundian Twitch
                last edited by

                @Khundian-Twitch ,
                Just a note, and I’m not sure if this is relevant with what you’re doing with pythonscript, but the calltip functions are triggered by a delimiter insertion, meaning when you type the keyword, and then click a parens if it is the opening for a function, then the calltip pops up, and you have to use the mouse to click the buttons up or down.

                What you’re doing, may not make what I just mentioned relevant, since it sounds like you’re past my knowledge, but I thought I might mention in in case it is relevant.

                1 Reply Last reply Reply Quote 2
                • mpheathM
                  mpheath @Khundian Twitch
                  last edited by mpheath

                  @Khundian-Twitch said in Calltip on mouse-over from UDL xml file (Python):

                  I did a small test with the SCINTILLANOTIFICATION.CALLTIPCLICK and this was the result, position 0 is clicked anywhere but the arrows, position 1 is clicked on the up arrow and position 2 should be clicked on the down arrow… but this results in Notepad++ crashing to desktop.

                  Exits with

                  Exception Code: c0000094

                  which is a division by zero code. Seems the position key cannot return a value 2 as it fails to get a value.

                  Also a second error

                  Exception Code: c000041d

                  A STATUS_FATAL_USER_CALLBACK_EXCEPTION code.

                  This is what I tested with:

                  from Npp import console, editor, notepad, SCINTILLANOTIFICATION
                  
                  try:
                      callback_registry
                  except:
                      callback_registry = []
                  
                  calltip_wrap = ['\x01 one\n\x02 description', '\x01 two\n\x02 description']
                  
                  if not editor.callTipActive():
                      editor.callTipShow(editor.getCurrentPos(), calltip_wrap[0])
                  
                  def calltip_update(args):
                      console.write(args)
                      pos = editor.getCurrentPos()
                  
                      if editor.callTipActive():
                          if args['position'] == 2:
                              editor.callTipCancel()
                              editor.callTipShow(pos, calltip_wrap[0])
                          elif args['position'] == 1:
                              editor.callTipCancel()
                              editor.callTipShow(pos, calltip_wrap[1])
                  
                      return True
                  
                  if 'calltip_update' not in callback_registry:
                      callback_registry.append('calltip_update')
                      editor.callback(calltip_update, [SCINTILLANOTIFICATION.CALLTIPCLICK])
                  
                  Khundian TwitchK 1 Reply Last reply Reply Quote 2
                  • Khundian TwitchK
                    Khundian Twitch @mpheath
                    last edited by

                    @mpheath Thanks for taking the time to test, reply and clarify the issue.
                    I guess I’m out of luck. I wonder if it’s something with the PythonScript plugin, when I have some time I’m gonna test with the LuaScript plugin and see if the same happens when creating a calltip.

                    1 Reply Last reply Reply Quote 0
                    • Khundian TwitchK
                      Khundian Twitch
                      last edited by

                      I tested with the LuaScript plugin and exactly the same happens, I guess it has nothing to do with either Python or LUA plugin. I guess its a bug in Scintilla or Notepad++. I’ll post the LUA code I used to test below.

                      function showcalltip(ch)
                      	calltip = "\001 <Click> \002"
                      	pos = editor.CurrentPos
                      	editor:CallTipShow(pos, calltip)
                          return false
                      end
                      
                      function printclick(flag)
                          print(flag)
                          return false
                      end
                      
                      print("Test script running.")
                       
                      npp.AddEventHandler("OnChar", showcalltip)
                      npp.AddEventHandler("OnCallTipClick", printclick)
                      
                      Alan KilbornA 1 Reply Last reply Reply Quote 1
                      • Alan KilbornA
                        Alan Kilborn @Khundian Twitch
                        last edited by

                        @Khundian-Twitch said in Calltip on mouse-over from UDL xml file (Python):

                        I guess its a bug in Scintilla or Notepad++

                        A crash is a pretty big deal; someone should open an official bug report on github.

                        Khundian TwitchK 1 Reply Last reply Reply Quote 2
                        • Khundian TwitchK
                          Khundian Twitch @Alan Kilborn
                          last edited by Khundian Twitch

                          @Alan-Kilborn said in Calltip on mouse-over from UDL xml file (Python):

                          @Khundian-Twitch said in Calltip on mouse-over from UDL xml file (Python):

                          I guess its a bug in Scintilla or Notepad++

                          A crash is a pretty big deal; someone should open an official bug report on github.

                          I agree, but I’m not sure if this qualifies as a bug in Notepad++?
                          The standard implementation of using the arrows to cycle through multiple overloads works fine. Its only when a user starts tinkering with script plugins and creating their own calltips that this crash will occur.

                          1 Reply Last reply Reply Quote 1
                          • Khundian TwitchK
                            Khundian Twitch
                            last edited by

                            I created a bug report on github, guess we’ll see if it qualifies as a bug. Link to report

                            1 Reply Last reply Reply Quote 3
                            • Khundian TwitchK
                              Khundian Twitch
                              last edited by

                              @mpheath Seems it was something in the Notepad++ code, but solved very quickly by the guys on github. Without a doubt the debug info you provided had something to do with that, Thanks again! :)

                              mpheathM 1 Reply Last reply Reply Quote 2
                              • mpheathM
                                mpheath @Khundian Twitch
                                last edited by mpheath

                                @Khundian-Twitch Well done! I tested the artifact in PR #14667 that solves the crash issue and so now position 2 is possible for the down arrow of the calltip.

                                1 Reply Last reply Reply Quote 3
                                • Khundian TwitchK
                                  Khundian Twitch
                                  last edited by

                                  I have finished the script, if anyone is interested I posted it in the Plugin development forum.
                                  Thanks again to everyone who helped me out!

                                  1 Reply Last reply Reply Quote 2
                                  • First post
                                    Last post
                                  The Community of users of the Notepad++ text editor.
                                  Powered by NodeBB | Contributors