How to Find Words and Go to Line in QScintilla Editor?

798 Views Asked by At

I would like to create a function for searching for words and highlight them in Qscintilla editor and another function to go to specific line in the editor.

I know these functions works in QPlainTextEdit but since I am learning Qscintilla and most of the documents are written in C++, it's difficult for me to understand and the methods are quite different in Scintilla.

import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.Qsci import *
#from PyQt5.QtCore import *

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 600)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
        self.gridLayout.setObjectName("gridLayout")
        self.textEdit = QsciScintilla(self.centralwidget)
        self.textEdit.setObjectName("textEdit")
        self.gridLayout.addWidget(self.textEdit, 0, 0, 1, 1)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))
        self.menubar.setObjectName("menubar")
        self.menuFile = QtWidgets.QMenu(self.menubar)
        self.menuFile.setObjectName("menuFile")
        self.menuSearch = QtWidgets.QMenu(self.menubar)
        self.menuSearch.setObjectName("menuSearch")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)
        self.actionOpen = QtWidgets.QAction(MainWindow)
        self.actionOpen.setObjectName("actionOpen")
        self.actionSave = QtWidgets.QAction(MainWindow)
        self.actionSave.setObjectName("actionSave")
        self.actionExit = QtWidgets.QAction(MainWindow)
        self.actionExit.setObjectName("actionExit")
        self.actionFind = QtWidgets.QAction(MainWindow)
        self.actionFind.setObjectName("actionFind")
        self.actionWord_Count = QtWidgets.QAction(MainWindow)
        self.actionWord_Count.setObjectName("actionWord_Count")
        self.actionNew = QtWidgets.QAction(MainWindow)
        self.actionNew.setObjectName("actionNew")
        self.menuFile.addAction(self.actionNew)
        self.menuFile.addAction(self.actionOpen)
        self.menuFile.addAction(self.actionSave)
        self.menuFile.addAction(self.actionExit)
        self.menuSearch.addAction(self.actionFind)
        self.menuSearch.addAction(self.actionWord_Count)
        self.menubar.addAction(self.menuFile.menuAction())
        self.menubar.addAction(self.menuSearch.menuAction())

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.menuFile.setTitle(_translate("MainWindow", "File"))
        self.menuSearch.setTitle(_translate("MainWindow", "Search"))
        self.actionOpen.setText(_translate("MainWindow", "Open"))
        self.actionOpen.setShortcut(_translate("MainWindow", "Ctrl+O"))
        self.actionSave.setText(_translate("MainWindow", "Save"))
        self.actionSave.setShortcut(_translate("MainWindow", "Ctrl+S"))
        self.actionExit.setText(_translate("MainWindow", "Exit"))
        self.actionExit.setShortcut(_translate("MainWindow", "Ctrl+Q"))
        self.actionFind.setText(_translate("MainWindow", "Find"))
        self.actionFind.setShortcut(_translate("MainWindow", "Ctrl+F"))
        self.actionWord_Count.setText(_translate("MainWindow", "Go to Line"))
        self.actionNew.setText(_translate("MainWindow", "New"))
        self.actionNew.setShortcut(_translate("MainWindow", "Ctrl+N"))

class Ui_Dock_Find(object):
    def setupUi(self, Dock_Find):
        Dock_Find.setObjectName("Dock_Find")
        Dock_Find.resize(320, 65)
        Dock_Find.setMinimumSize(QtCore.QSize(320, 65))
        font = QtGui.QFont()
        font.setPointSize(10)
        Dock_Find.setFont(font)
        icon = QtGui.QIcon()

        icon.addPixmap(QtGui.QPixmap("Ok.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)

        Dock_Find.setWindowIcon(icon)
        self.dockWidgetContents = QtWidgets.QWidget()
        self.dockWidgetContents.setObjectName("dockWidgetContents")
        self.serachLabel = QtWidgets.QLabel(self.dockWidgetContents)
        self.serachLabel.setGeometry(QtCore.QRect(10, 10, 71, 16))
        self.serachLabel.setObjectName("serachLabel")
        self.findLine = QtWidgets.QLineEdit(self.dockWidgetContents)
        self.findLine.setGeometry(QtCore.QRect(80, 10, 151, 20))
        self.findLine.setObjectName("findLine")
        self.findButton = QtWidgets.QPushButton(self.dockWidgetContents)
        self.findButton.setGeometry(QtCore.QRect(240, 10, 75, 23))
        self.findButton.setObjectName("findButton")
        Dock_Find.setWidget(self.dockWidgetContents)

        self.retranslateUi(Dock_Find)
        QtCore.QMetaObject.connectSlotsByName(Dock_Find)

    def retranslateUi(self, Dock_Find):
        _translate = QtCore.QCoreApplication.translate
        Dock_Find.setWindowTitle(_translate("Dock_Find", "Find"))
        self.serachLabel.setText(_translate("Dock_Find", "Search For:"))
        self.findButton.setText(_translate("Dock_Find", "Find"))


class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setupUi(self)

        #Defining Menu Actions
        self.actionNew.triggered.connect(self.newFile)
        self.actionOpen.triggered.connect(self.openFile)
        #self.actionSave.triggered.connect(self.saveFile)
        #self.actionExit.triggered.connect(self.exitFile)
        self.actionFind.triggered.connect(self.findWord)
        self.actionWord_Count.triggered.connect(self.gotoLine)
        self.showMaximized()



    def newFile(self):
        self.textEdit.clear()

    def openFile(self):
        filename = QFileDialog.getOpenFileName(self, 'Open File', ".","(*.txt *.py *.log *.csv)")
        if filename[0]:
            #wordcounts = Counter()
            with open(filename[0], 'rt') as fp:
                for line in fp:
                    #wordcounts.update(line.split())
                    data = fp.read()
                    self.textEdit.setText(data)


    def saveFile(self):
        filename = QFileDialog.getSaveFileName(self, 'Save File', ".", "(*.txt)")
        if filename[0]:
            f = open(filename[0], 'wt')
            with f:
                text = self.textEdit.toPlainText()
                f.write(text)
                QMessageBox.about(self, "Save File", "File Saved Successfully")

    def exitFile(self):
        choice = QMessageBox.question(self, 'Close', "Do you want to close?", QMessageBox.Yes | QMessageBox.No)
        if choice == QMessageBox.Yes:
            self.saveFile()
            self.close()
        else:
            pass

    def findWord(self):

        self.dock = Dock_Find()
        self.addDockWidget(Qt.TopDockWidgetArea, self.dock)
        self.dock.show()

        def handleFind():

            word = self.dock.findLine.text()
            if self.textEdit.SCI_FINDTEXT(self.textEdit.SCFIND_WHOLEWORD(), word):
                self.textEdit.SCFIND_WHOLEWORD(word)

            else:
                self.textEdit.SCFIND_WHOLEWORD(word)
                return

        self.dock.findButton.clicked.connect(handleFind)

    def gotoLine(self):
        window = GoTo()
        window.show()
        def handleGoTo():
            ln = int(window.findText.text())
            """messenger = self.textEdit.SendScintilla()
            messenger(self.textEdit.SCI_SETTARGETSTART, 0)
            messenger(self.textEdit.SCI_SETTARGETEND, len(self.textEdit))
            pos = messenger(self.textEdit.SCI_SEARCHINTARGET, len("ln"), "ln")
            print(pos)"""
            linecursor = self.textEdit.wordAtLineIndex(ln, self.textEdit.getCursorPosition()[1])
            print(linecursor)
            self.textEdit.SCI_GOTOPOS(window.findText.text())
            window.hide()
        window.findButton.clicked.connect(handleGoTo)


class Dock_Find(QtWidgets.QDockWidget, Ui_Dock_Find):
    def __init__(self, parent=None):
        super(Dock_Find, self).__init__(parent)
        self.setupUi(self)
        self.findLine.setPlaceholderText("Type Here")

class GoTo(QDialog):
    def __init__(self):
        super(GoTo, self).__init__()
        self.initUI()
        self.setWindowTitle("GoTo")
        icon = QIcon()
        icon.addPixmap(QPixmap(":/image/graphy_100px.png"), QIcon.Normal, QIcon.Off)
        self.setWindowIcon(icon)

    def initUI(self):
        self.lb1 = QLabel("Go To Line:", self)
        self.lb1.setStyleSheet("font-size: 15px")
        self.lb1.move(10, 10)
        self.findText = QLineEdit(self)
        self.findText.move(10, 40)
        self.findText.resize(200, 20)
        self.findText.setPlaceholderText('Type the line number')
        self.findText.setClearButtonEnabled(True)
        self.findText.setInputMask("9999999999") #For allowing up to number digit only as input
        self.findButton = QPushButton('GO', self)
        self.findButton.move(220, 40)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    QApplication.setStyle(QStyleFactory.create('Fusion'))
    myGUI = MainWindow()

    sys.exit(app.exec_())

I don't understand how to implement these two functions and I have been trying, but no positive result for finding the word. However for second function, I can print the word which starts with the line number but I want to highlight that word with line number when the function go to line is searched.

1

There are 1 best solutions below

0
Misinahaiya On

First thing first, code snippets appeared in those C++ documentations can be easily translated to Python code snippets.

Firstly, let me point out that your code has several problems:

  1. You have entered the code self.text.SCFIND_WHOLEWORD() in your finding module, which tends to call the method, as considered by the Python interpreter.
Traceback (most recent call last):
  File "/Users/USER/Documents/code.py", line 165, in handleFind
    if self.textEdit.SCI_FINDTEXT(self.textEdit.SCFIND_WHOLEWORD(), word):
                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: 'int' object is not callable
  1. You have also entered the snippet self.textEdit.SCI_GOTOPOS() in your go-to module, which is also tend to be called as a function even it isn't a function and doesn't have the ability to do so.
Traceback (most recent call last):
  File "/Users/USER/Documents/code.py", line 186, in handleGoTo
    self.textEdit.SCI_GOTOPOS(window.findText.text())
TypeError: 'int' object is not callable

Solutions for your problems

Find whole-word problems

According to the documentation, the QsciScintilla widget and its framework, QScintilla, have already provided you a complete-set of finding utilities. For example, your desired functionality, as commented above by @ekhumoro, looks like the following.

QsciScintilla.findFirst(
    expr: str,
    re: bool,
    cs: bool,
    wo: bool,
    wrap: bool,
    forward: bool = True,
    line: int = -1,
    index: int = -1,
    show: bool = True,
    posix: bool = False,
    cxx11: bool = False 
) -> bool

Consider the above parameters index.

  • required expr - the string that you want to find.
  • required re - if enabled, regular-expression find mode is enabled.
  • required cs - if enabled, case-sensitive search mode is enabled.
  • required wo - if enabled, whole-word-only search mode is enabled.
  • required wrap - if enabled, the search will wrap around the end of the text.
  • forward - if True, the search occurs forward; otherwise backward from the current position of the text, or the line and index of the text.
  • line - if negative (invalid), the search will start at the current position of the text. Else it starts with the index (column) of the line. Counted from 0.
  • index - See the line parameter.
  • posix - if enabled, the search will be treated as a POSIX-compatible manner.
  • cxx11 - if enabled and the re option is enabled, the ReGeX is treated as the C++11 standard regular expression.

The method will return True if any text occurrence is found; otherwise False.

Notice that if you fill invalid types (e.g. even int and bool), a ValueError is thrown.

After the consideration, replace your code,

    164 |              if self.textEdit.SCI_FINDTEXT(self.textEdit.SCFIND_WHOLEWORD(), word):
    165 |                  self.textEdit.SCFIND_WHOLEWORD(word)
    166 | 
    167 |              else:
    168 |                  self.textEdit.SCFIND_WHOLEWORD(word)
    169 |                  return

with

    164 |             self.textEdit.findFirst(
    165 |                 word,
    166 |                 False,  # No Regular expression.
    167 |                 False,  # No case sensitivity.
    168 |                 True,   # Whole-word search enabled.
    169 |                 True,   # Text are wrapped.
    170 |             )

Goto line problem

According to the documentation, we have the setCursorPosition method (the method is so straightforward that I won't list them here).

So, replace your code

    179 |             """messenger = self.textEdit.SendScintilla()
    180 |             messenger(self.textEdit.SCI_SETTARGETSTART, 0)
    181 |             messenger(self.textEdit.SCI_SETTARGETEND, len(self.textEdit))
    182 |             pos = messenger(self.textEdit.SCI_SEARCHINTARGET, len("ln"), "ln")
    183 |             print(pos)"""
    184 |             linecursor = self.textEdit.wordAtLineIndex(ln, self.textEdit.getCursorPosition()[1])
    185 |             print(linecursor)
    186 |             self.textEdit.SCI_GOTOPOS(window.findText.text())
    187 |             window.hide()

with

    179 |             self.textEdit.setCursorPosition(ln, 0)

This single-line comment is basically and ideally, same as your ten-lines one.

Comment me if your problem is, still, no solved.