We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Concordance: Index Error
<!--
This is an issue template. Please fill in the relevant details in the
sections below.
-->

##### Text version
<!-- From menu _Options→Add-ons→Orange3-Text_ or code `orangecontrib.text.version.full_version` -->
0.3.0

##### Orange version
<!-- From menu _Help→About→Version_ or code `Orange.version.full_version` -->
3.15.dev

##### Expected behavior
No crash.


##### Actual behavior
Concordance crashes when a document is selected and the user removes query.


##### Steps to reproduce the behavior
Corpus (bookexcertps) - query for word - select a document from the visualization - remove the word (delete a character).


##### Additional info (worksheets, data, screenshots, ...)




--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `orangecontrib/text/widgets/owconcordance.py`
Content:
```
1 from typing import Optional
2 
3 from itertools import chain
4 from AnyQt.QtCore import Qt, QAbstractTableModel, QSize, QItemSelectionModel, \
5     QItemSelection, QModelIndex
6 from AnyQt.QtWidgets import QSizePolicy, QApplication, QTableView, \
7     QStyledItemDelegate
8 from AnyQt.QtGui import QColor
9 
10 from Orange.widgets import gui
11 from Orange.widgets.settings import Setting, ContextSetting, PerfectDomainContextHandler
12 from Orange.widgets.widget import OWWidget, Msg, Input, Output
13 from nltk import ConcordanceIndex
14 from orangecontrib.text.corpus import Corpus
15 from orangecontrib.text.topics import Topic
16 from orangecontrib.text.preprocess import WordPunctTokenizer
17 
18 
19 class HorizontalGridDelegate(QStyledItemDelegate):
20     """Class for setting elide."""
21 
22     def paint(self, painter, option, index):
23         if index.column() == 0:
24             option.textElideMode = Qt.ElideLeft
25         elif index.column() == 2:
26             option.textElideMode = Qt.ElideRight
27         QStyledItemDelegate.paint(self, painter, option, index)
28 
29 
30 class DocumentSelectionModel(QItemSelectionModel):
31     """Sets selection for QTableView. Creates a set of selected documents."""
32 
33     def select(self, selection, flags):
34         # which rows have been selected
35         indexes = selection.indexes() if isinstance(selection, QItemSelection) \
36                   else [selection]
37         # prevent crashing when deleting the connection
38         if not indexes:
39             super().select(selection, flags)
40             return
41         # indexes[0].row() == -1 indicates clicking outside of the table
42         if len(indexes) == 1 and indexes[0].row() == -1:
43             self.clear()
44             return
45         word_index = self.model().word_index
46         selected_docs = {word_index[index.row()][0] for index in indexes}
47         selected_rows = [
48             row_index for row_index, (doc_index, _) in enumerate(word_index)
49             if doc_index in selected_docs]
50         selection = QItemSelection()
51         # select all rows belonging to the selected document
52         for row in selected_rows:
53             index = self.model().index(row, 0)
54             selection.select(index, index)
55         super().select(selection, flags)
56 
57 
58 class ConcordanceModel(QAbstractTableModel):
59     """A model for constructing concordances from text."""
60 
61     def __init__(self):
62         QAbstractTableModel.__init__(self)
63         self.word = None
64         self.corpus = None
65         self.tokens = None
66         self.n_tokens = None
67         self.n_types = None
68         self.indices = None
69         self.word_index = None
70         self.width = 8
71         self.colored_rows = None
72 
73     def set_word(self, word):
74         self.modelAboutToBeReset.emit()
75         self.word = word
76         self._compute_word_index()
77         self.modelReset.emit()
78 
79     def set_corpus(self, corpus):
80         self.modelAboutToBeReset.emit()
81         self.corpus = corpus
82         self.set_tokens()
83         self._compute_indices()
84         self._compute_word_index()
85         self.modelReset.emit()
86 
87     def set_tokens(self):
88         if self.corpus is None:
89             self.tokens = None
90             return
91         tokenizer = WordPunctTokenizer()
92         self.tokens = tokenizer(self.corpus.documents)
93         self.n_tokens = sum(map(len, self.tokens))
94         self.n_types = len(set(chain.from_iterable(self.tokens)))
95 
96     def set_width(self, width):
97         self.modelAboutToBeReset.emit()
98         self.width = width
99         self.modelReset.emit()
100 
101     def flags(self, _):
102         return Qt.ItemIsEnabled | Qt.ItemIsSelectable
103 
104     def rowCount(self, parent=QModelIndex(), *args, **kwargs):
105         return 0 if parent.isValid() or self.word_index is None else len(self.word_index)
106 
107     def columnCount(self, parent=None, *args, **kwargs):
108         return 3
109 
110     def data(self, index, role=Qt.DisplayRole):
111         row, col = index.row(), index.column()
112         doc, index = self.word_index[row]
113 
114         if role == Qt.DisplayRole:
115             tokens = self.tokens
116             if col == 0:
117                 return ' '.join(tokens[doc][max(index - self.width, 0):index])
118             if col == 1:
119                 return tokens[doc][index]
120             if col == 2:
121                 return ' '.join(tokens[doc][index + 1:index + self.width + 1])
122 
123         elif role == Qt.TextAlignmentRole:
124             return [Qt.AlignRight | Qt.AlignVCenter,
125                     Qt.AlignCenter,
126                     Qt.AlignLeft | Qt.AlignVCenter][col]
127 
128         elif role == Qt.BackgroundRole:
129             const = self.word_index[row][0] in self.colored_rows
130             return QColor(236 + 19 * const, 243 + 12 * const, 255)
131 
132     def _compute_indices(self):  # type: () -> Optional[None, list]
133         if self.corpus is None:
134             self.indices = None
135             return
136         self.indices = [ConcordanceIndex(doc, key=lambda x: x.lower())
137                         for doc in self.tokens]
138 
139     def _compute_word_index(self):
140         if self.indices is None or self.word is None:
141             self.word_index = self.colored_rows = None
142         else:
143             self.word_index = [
144                 (doc_idx, offset) for doc_idx, doc in enumerate(self.indices)
145                 for offset in doc.offsets(self.word)]
146             self.colored_rows = set(sorted({d[0] for d in self.word_index})[::2])
147 
148     def matching_docs(self):
149         if self.indices and self.word:
150             return sum(bool(doc.offsets(self.word)) for doc in self.indices)
151         else:
152             return 0
153 
154 
155 class OWConcordance(OWWidget):
156     name = "Concordance"
157     description = "Display the context of the word."
158     icon = "icons/Concordance.svg"
159     priority = 520
160 
161     class Inputs:
162         corpus = Input("Corpus", Corpus)
163         query_word = Input("Query Word", Topic)
164 
165     class Outputs:
166         selected_documents = Output("Selected Documents", Corpus)
167 
168     settingsHandler = PerfectDomainContextHandler(
169         match_values = PerfectDomainContextHandler.MATCH_VALUES_ALL
170     )
171     autocommit = Setting(True)
172     context_width = Setting(5)
173     word = ContextSetting("", exclude_metas=False)
174     selected_rows = Setting([], schema_only=True)
175 
176     class Warning(OWWidget.Warning):
177         multiple_words_on_input = Msg("Multiple query words on input. "
178                                       "Only the first one is considered!")
179 
180     def __init__(self):
181         super().__init__()
182 
183         self.corpus = None      # Corpus
184         self.n_matching = ''    # Info on docs matching the word
185         self.n_tokens = ''      # Info on tokens
186         self.n_types = ''       # Info on types (unique tokens)
187         self.is_word_on_input = False
188 
189         # Info attributes
190         info_box = gui.widgetBox(self.controlArea, 'Info')
191         gui.label(info_box, self, 'Tokens: %(n_tokens)s')
192         gui.label(info_box, self, 'Types: %(n_types)s')
193         gui.label(info_box, self, 'Matching: %(n_matching)s')
194 
195         # Width parameter
196         gui.spin(self.controlArea, self, 'context_width', 3, 10, box=True,
197                  label="Number of words:", callback=self.set_width)
198 
199         gui.rubber(self.controlArea)
200 
201         # Search
202         c_box = gui.widgetBox(self.mainArea, orientation="vertical")
203         self.input = gui.lineEdit(
204             c_box, self, 'word', orientation=Qt.Horizontal,
205             sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding,
206                                    QSizePolicy.Fixed),
207             label='Query:', callback=self.set_word, callbackOnType=True)
208         self.input.setFocus()
209 
210         # Concordances view
211         self.conc_view = QTableView()
212         self.model = ConcordanceModel()
213         self.conc_view.setModel(self.model)
214         self.conc_view.setWordWrap(False)
215         self.conc_view.setSelectionBehavior(QTableView.SelectRows)
216         self.conc_view.setSelectionModel(DocumentSelectionModel(self.model))
217         self.conc_view.setItemDelegate(HorizontalGridDelegate())
218         self.conc_view.selectionModel().selectionChanged.connect(self.selection_changed)
219         self.conc_view.horizontalHeader().hide()
220         self.conc_view.setShowGrid(False)
221         self.mainArea.layout().addWidget(self.conc_view)
222         self.set_width()
223 
224         # Auto-commit box
225         gui.auto_commit(self.controlArea, self, 'autocommit', 'Commit',
226                         'Auto commit is on')
227 
228     def sizeHint(self): # pragma: no cover
229         return QSize(600, 400)
230 
231     def set_width(self):
232         sel = self.conc_view.selectionModel().selection()
233         self.model.set_width(self.context_width)
234         if sel:
235             self.conc_view.selectionModel().select(sel,
236                 QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows)
237 
238     def selection_changed(self):
239         selection = self.conc_view.selectionModel().selection()
240         self.selected_rows = sorted(set(cell.row() for cell in selection.indexes()))
241         self.commit()
242 
243     def set_selection(self, selection):
244         if selection:
245             sel = QItemSelection()
246             for row in selection:
247                 index = self.conc_view.model().index(row, 0)
248                 sel.select(index, index)
249             self.conc_view.selectionModel().select(sel,
250                 QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows)
251 
252     @Inputs.corpus
253     def set_corpus(self, data=None):
254         self.closeContext()
255         self.corpus = data
256         if data is None:    # data removed, clear selection
257             self.selected_rows = []
258 
259         if not self.is_word_on_input:
260             self.word = ""
261             self.openContext(self.corpus)
262 
263         self.model.set_corpus(self.corpus)
264         self.set_word()
265 
266     @Inputs.query_word
267     def set_word_from_input(self, topic):
268         self.Warning.multiple_words_on_input.clear()
269         if self.is_word_on_input:   # word changed, clear selection
270             self.selected_rows = []
271         self.is_word_on_input = topic is not None and len(topic) > 0
272         self.input.setEnabled(not self.is_word_on_input)
273         if self.is_word_on_input:
274             if len(topic) > 1:
275                 self.Warning.multiple_words_on_input()
276             self.word = topic.metas[0, 0]
277             self.set_word()
278 
279     def set_word(self):
280         self.model.set_word(self.word)
281         self.update_widget()
282         self.commit()
283 
284     def handleNewSignals(self):
285         self.set_selection(self.selected_rows)
286 
287     def resize_columns(self):
288         col_width = (self.conc_view.width() -
289                      self.conc_view.columnWidth(1)) / 2 - 12
290         self.conc_view.setColumnWidth(0, col_width)
291         self.conc_view.setColumnWidth(2, col_width)
292 
293     def resizeEvent(self, event): # pragma: no cover
294         super().resizeEvent(event)
295         self.resize_columns()
296 
297     def update_widget(self):
298         self.conc_view.resizeColumnToContents(1)
299         self.resize_columns()
300         self.conc_view.resizeRowsToContents()
301 
302         if self.corpus is not None:
303             self.n_matching = '{}/{}'.format(
304                 self.model.matching_docs() if self.word else 0,
305                 len(self.corpus))
306             self.n_tokens = self.model.n_tokens
307             self.n_types = self.model.n_types
308         else:
309             self.n_matching = ''
310             self.n_tokens = ''
311             self.n_types = ''
312 
313     def commit(self):
314         selected_docs = sorted(set(self.model.word_index[row][0]
315                                    for row in self.selected_rows))
316         if selected_docs:
317             selected = self.corpus[selected_docs]
318             self.Outputs.selected_documents.send(selected)
319         else:
320             self.Outputs.selected_documents.send(None)
321 
322     def send_report(self):
323         view = self.conc_view
324         model = self.conc_view.model()
325         self.report_items("Concordances", (
326             ("Query", model.word),
327             ("Tokens", model.n_tokens),
328             ("Types", model.n_types),
329             ("Matching", self.n_matching),
330         ))
331         self.report_table(view)
332 
333 
334 if __name__ == '__main__': # pragma: no cover
335     app = QApplication([])
336     widget = OWConcordance()
337     corpus = Corpus.from_file('book-excerpts')
338     corpus = corpus[:3]
339     widget.set_corpus(corpus)
340     widget.show()
341     app.exec()
342 
343
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/orangecontrib/text/widgets/owconcordance.py b/orangecontrib/text/widgets/owconcordance.py
--- a/orangecontrib/text/widgets/owconcordance.py
+++ b/orangecontrib/text/widgets/owconcordance.py
@@ -277,6 +277,7 @@
             self.set_word()
 
     def set_word(self):
+        self.selected_rows = []
         self.model.set_word(self.word)
         self.update_widget()
         self.commit()

{"golden_diff": "diff --git a/orangecontrib/text/widgets/owconcordance.py b/orangecontrib/text/widgets/owconcordance.py\n--- a/orangecontrib/text/widgets/owconcordance.py\n+++ b/orangecontrib/text/widgets/owconcordance.py\n@@ -277,6 +277,7 @@\n             self.set_word()\n \n     def set_word(self):\n+        self.selected_rows = []\n         self.model.set_word(self.word)\n         self.update_widget()\n         self.commit()\n", "issue": "Concordance: Index Error\n<!--\r\nThis is an issue template. Please fill in the relevant details in the\r\nsections below.\r\n-->\r\n\r\n##### Text version\r\n<!-- From menu _Options\u2192Add-ons\u2192Orange3-Text_ or code `orangecontrib.text.version.full_version` -->\r\n0.3.0\r\n\r\n##### Orange version\r\n<!-- From menu _Help\u2192About\u2192Version_ or code `Orange.version.full_version` -->\r\n3.15.dev\r\n\r\n##### Expected behavior\r\nNo crash.\r\n\r\n\r\n##### Actual behavior\r\nConcordance crashes when a document is selected and the user removes query.\r\n\r\n\r\n##### Steps to reproduce the behavior\r\nCorpus (bookexcertps) - query for word - select a document from the visualization - remove the word (delete a character).\r\n\r\n\r\n##### Additional info (worksheets, data, screenshots, ...)\r\n\r\n\r\n\n", "before_files": [{"content": "from typing import Optional\n\nfrom itertools import chain\nfrom AnyQt.QtCore import Qt, QAbstractTableModel, QSize, QItemSelectionModel, \\\n    QItemSelection, QModelIndex\nfrom AnyQt.QtWidgets import QSizePolicy, QApplication, QTableView, \\\n    QStyledItemDelegate\nfrom AnyQt.QtGui import QColor\n\nfrom Orange.widgets import gui\nfrom Orange.widgets.settings import Setting, ContextSetting, PerfectDomainContextHandler\nfrom Orange.widgets.widget import OWWidget, Msg, Input, Output\nfrom nltk import ConcordanceIndex\nfrom orangecontrib.text.corpus import Corpus\nfrom orangecontrib.text.topics import Topic\nfrom orangecontrib.text.preprocess import WordPunctTokenizer\n\n\nclass HorizontalGridDelegate(QStyledItemDelegate):\n    \"\"\"Class for setting elide.\"\"\"\n\n    def paint(self, painter, option, index):\n        if index.column() == 0:\n            option.textElideMode = Qt.ElideLeft\n        elif index.column() == 2:\n            option.textElideMode = Qt.ElideRight\n        QStyledItemDelegate.paint(self, painter, option, index)\n\n\nclass DocumentSelectionModel(QItemSelectionModel):\n    \"\"\"Sets selection for QTableView. Creates a set of selected documents.\"\"\"\n\n    def select(self, selection, flags):\n        # which rows have been selected\n        indexes = selection.indexes() if isinstance(selection, QItemSelection) \\\n                  else [selection]\n        # prevent crashing when deleting the connection\n        if not indexes:\n            super().select(selection, flags)\n            return\n        # indexes[0].row() == -1 indicates clicking outside of the table\n        if len(indexes) == 1 and indexes[0].row() == -1:\n            self.clear()\n            return\n        word_index = self.model().word_index\n        selected_docs = {word_index[index.row()][0] for index in indexes}\n        selected_rows = [\n            row_index for row_index, (doc_index, _) in enumerate(word_index)\n            if doc_index in selected_docs]\n        selection = QItemSelection()\n        # select all rows belonging to the selected document\n        for row in selected_rows:\n            index = self.model().index(row, 0)\n            selection.select(index, index)\n        super().select(selection, flags)\n\n\nclass ConcordanceModel(QAbstractTableModel):\n    \"\"\"A model for constructing concordances from text.\"\"\"\n\n    def __init__(self):\n        QAbstractTableModel.__init__(self)\n        self.word = None\n        self.corpus = None\n        self.tokens = None\n        self.n_tokens = None\n        self.n_types = None\n        self.indices = None\n        self.word_index = None\n        self.width = 8\n        self.colored_rows = None\n\n    def set_word(self, word):\n        self.modelAboutToBeReset.emit()\n        self.word = word\n        self._compute_word_index()\n        self.modelReset.emit()\n\n    def set_corpus(self, corpus):\n        self.modelAboutToBeReset.emit()\n        self.corpus = corpus\n        self.set_tokens()\n        self._compute_indices()\n        self._compute_word_index()\n        self.modelReset.emit()\n\n    def set_tokens(self):\n        if self.corpus is None:\n            self.tokens = None\n            return\n        tokenizer = WordPunctTokenizer()\n        self.tokens = tokenizer(self.corpus.documents)\n        self.n_tokens = sum(map(len, self.tokens))\n        self.n_types = len(set(chain.from_iterable(self.tokens)))\n\n    def set_width(self, width):\n        self.modelAboutToBeReset.emit()\n        self.width = width\n        self.modelReset.emit()\n\n    def flags(self, _):\n        return Qt.ItemIsEnabled | Qt.ItemIsSelectable\n\n    def rowCount(self, parent=QModelIndex(), *args, **kwargs):\n        return 0 if parent.isValid() or self.word_index is None else len(self.word_index)\n\n    def columnCount(self, parent=None, *args, **kwargs):\n        return 3\n\n    def data(self, index, role=Qt.DisplayRole):\n        row, col = index.row(), index.column()\n        doc, index = self.word_index[row]\n\n        if role == Qt.DisplayRole:\n            tokens = self.tokens\n            if col == 0:\n                return ' '.join(tokens[doc][max(index - self.width, 0):index])\n            if col == 1:\n                return tokens[doc][index]\n            if col == 2:\n                return ' '.join(tokens[doc][index + 1:index + self.width + 1])\n\n        elif role == Qt.TextAlignmentRole:\n            return [Qt.AlignRight | Qt.AlignVCenter,\n                    Qt.AlignCenter,\n                    Qt.AlignLeft | Qt.AlignVCenter][col]\n\n        elif role == Qt.BackgroundRole:\n            const = self.word_index[row][0] in self.colored_rows\n            return QColor(236 + 19 * const, 243 + 12 * const, 255)\n\n    def _compute_indices(self):  # type: () -> Optional[None, list]\n        if self.corpus is None:\n            self.indices = None\n            return\n        self.indices = [ConcordanceIndex(doc, key=lambda x: x.lower())\n                        for doc in self.tokens]\n\n    def _compute_word_index(self):\n        if self.indices is None or self.word is None:\n            self.word_index = self.colored_rows = None\n        else:\n            self.word_index = [\n                (doc_idx, offset) for doc_idx, doc in enumerate(self.indices)\n                for offset in doc.offsets(self.word)]\n            self.colored_rows = set(sorted({d[0] for d in self.word_index})[::2])\n\n    def matching_docs(self):\n        if self.indices and self.word:\n            return sum(bool(doc.offsets(self.word)) for doc in self.indices)\n        else:\n            return 0\n\n\nclass OWConcordance(OWWidget):\n    name = \"Concordance\"\n    description = \"Display the context of the word.\"\n    icon = \"icons/Concordance.svg\"\n    priority = 520\n\n    class Inputs:\n        corpus = Input(\"Corpus\", Corpus)\n        query_word = Input(\"Query Word\", Topic)\n\n    class Outputs:\n        selected_documents = Output(\"Selected Documents\", Corpus)\n\n    settingsHandler = PerfectDomainContextHandler(\n        match_values = PerfectDomainContextHandler.MATCH_VALUES_ALL\n    )\n    autocommit = Setting(True)\n    context_width = Setting(5)\n    word = ContextSetting(\"\", exclude_metas=False)\n    selected_rows = Setting([], schema_only=True)\n\n    class Warning(OWWidget.Warning):\n        multiple_words_on_input = Msg(\"Multiple query words on input. \"\n                                      \"Only the first one is considered!\")\n\n    def __init__(self):\n        super().__init__()\n\n        self.corpus = None      # Corpus\n        self.n_matching = ''    # Info on docs matching the word\n        self.n_tokens = ''      # Info on tokens\n        self.n_types = ''       # Info on types (unique tokens)\n        self.is_word_on_input = False\n\n        # Info attributes\n        info_box = gui.widgetBox(self.controlArea, 'Info')\n        gui.label(info_box, self, 'Tokens: %(n_tokens)s')\n        gui.label(info_box, self, 'Types: %(n_types)s')\n        gui.label(info_box, self, 'Matching: %(n_matching)s')\n\n        # Width parameter\n        gui.spin(self.controlArea, self, 'context_width', 3, 10, box=True,\n                 label=\"Number of words:\", callback=self.set_width)\n\n        gui.rubber(self.controlArea)\n\n        # Search\n        c_box = gui.widgetBox(self.mainArea, orientation=\"vertical\")\n        self.input = gui.lineEdit(\n            c_box, self, 'word', orientation=Qt.Horizontal,\n            sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding,\n                                   QSizePolicy.Fixed),\n            label='Query:', callback=self.set_word, callbackOnType=True)\n        self.input.setFocus()\n\n        # Concordances view\n        self.conc_view = QTableView()\n        self.model = ConcordanceModel()\n        self.conc_view.setModel(self.model)\n        self.conc_view.setWordWrap(False)\n        self.conc_view.setSelectionBehavior(QTableView.SelectRows)\n        self.conc_view.setSelectionModel(DocumentSelectionModel(self.model))\n        self.conc_view.setItemDelegate(HorizontalGridDelegate())\n        self.conc_view.selectionModel().selectionChanged.connect(self.selection_changed)\n        self.conc_view.horizontalHeader().hide()\n        self.conc_view.setShowGrid(False)\n        self.mainArea.layout().addWidget(self.conc_view)\n        self.set_width()\n\n        # Auto-commit box\n        gui.auto_commit(self.controlArea, self, 'autocommit', 'Commit',\n                        'Auto commit is on')\n\n    def sizeHint(self): # pragma: no cover\n        return QSize(600, 400)\n\n    def set_width(self):\n        sel = self.conc_view.selectionModel().selection()\n        self.model.set_width(self.context_width)\n        if sel:\n            self.conc_view.selectionModel().select(sel,\n                QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows)\n\n    def selection_changed(self):\n        selection = self.conc_view.selectionModel().selection()\n        self.selected_rows = sorted(set(cell.row() for cell in selection.indexes()))\n        self.commit()\n\n    def set_selection(self, selection):\n        if selection:\n            sel = QItemSelection()\n            for row in selection:\n                index = self.conc_view.model().index(row, 0)\n                sel.select(index, index)\n            self.conc_view.selectionModel().select(sel,\n                QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows)\n\n    @Inputs.corpus\n    def set_corpus(self, data=None):\n        self.closeContext()\n        self.corpus = data\n        if data is None:    # data removed, clear selection\n            self.selected_rows = []\n\n        if not self.is_word_on_input:\n            self.word = \"\"\n            self.openContext(self.corpus)\n\n        self.model.set_corpus(self.corpus)\n        self.set_word()\n\n    @Inputs.query_word\n    def set_word_from_input(self, topic):\n        self.Warning.multiple_words_on_input.clear()\n        if self.is_word_on_input:   # word changed, clear selection\n            self.selected_rows = []\n        self.is_word_on_input = topic is not None and len(topic) > 0\n        self.input.setEnabled(not self.is_word_on_input)\n        if self.is_word_on_input:\n            if len(topic) > 1:\n                self.Warning.multiple_words_on_input()\n            self.word = topic.metas[0, 0]\n            self.set_word()\n\n    def set_word(self):\n        self.model.set_word(self.word)\n        self.update_widget()\n        self.commit()\n\n    def handleNewSignals(self):\n        self.set_selection(self.selected_rows)\n\n    def resize_columns(self):\n        col_width = (self.conc_view.width() -\n                     self.conc_view.columnWidth(1)) / 2 - 12\n        self.conc_view.setColumnWidth(0, col_width)\n        self.conc_view.setColumnWidth(2, col_width)\n\n    def resizeEvent(self, event): # pragma: no cover\n        super().resizeEvent(event)\n        self.resize_columns()\n\n    def update_widget(self):\n        self.conc_view.resizeColumnToContents(1)\n        self.resize_columns()\n        self.conc_view.resizeRowsToContents()\n\n        if self.corpus is not None:\n            self.n_matching = '{}/{}'.format(\n                self.model.matching_docs() if self.word else 0,\n                len(self.corpus))\n            self.n_tokens = self.model.n_tokens\n            self.n_types = self.model.n_types\n        else:\n            self.n_matching = ''\n            self.n_tokens = ''\n            self.n_types = ''\n\n    def commit(self):\n        selected_docs = sorted(set(self.model.word_index[row][0]\n                                   for row in self.selected_rows))\n        if selected_docs:\n            selected = self.corpus[selected_docs]\n            self.Outputs.selected_documents.send(selected)\n        else:\n            self.Outputs.selected_documents.send(None)\n\n    def send_report(self):\n        view = self.conc_view\n        model = self.conc_view.model()\n        self.report_items(\"Concordances\", (\n            (\"Query\", model.word),\n            (\"Tokens\", model.n_tokens),\n            (\"Types\", model.n_types),\n            (\"Matching\", self.n_matching),\n        ))\n        self.report_table(view)\n\n\nif __name__ == '__main__': # pragma: no cover\n    app = QApplication([])\n    widget = OWConcordance()\n    corpus = Corpus.from_file('book-excerpts')\n    corpus = corpus[:3]\n    widget.set_corpus(corpus)\n    widget.show()\n    app.exec()\n\n", "path": "orangecontrib/text/widgets/owconcordance.py"}], "after_files": [{"content": "from typing import Optional\n\nfrom itertools import chain\nfrom AnyQt.QtCore import Qt, QAbstractTableModel, QSize, QItemSelectionModel, \\\n    QItemSelection, QModelIndex\nfrom AnyQt.QtWidgets import QSizePolicy, QApplication, QTableView, \\\n    QStyledItemDelegate\nfrom AnyQt.QtGui import QColor\n\nfrom Orange.widgets import gui\nfrom Orange.widgets.settings import Setting, ContextSetting, PerfectDomainContextHandler\nfrom Orange.widgets.widget import OWWidget, Msg, Input, Output\nfrom nltk import ConcordanceIndex\nfrom orangecontrib.text.corpus import Corpus\nfrom orangecontrib.text.topics import Topic\nfrom orangecontrib.text.preprocess import WordPunctTokenizer\n\n\nclass HorizontalGridDelegate(QStyledItemDelegate):\n    \"\"\"Class for setting elide.\"\"\"\n\n    def paint(self, painter, option, index):\n        if index.column() == 0:\n            option.textElideMode = Qt.ElideLeft\n        elif index.column() == 2:\n            option.textElideMode = Qt.ElideRight\n        QStyledItemDelegate.paint(self, painter, option, index)\n\n\nclass DocumentSelectionModel(QItemSelectionModel):\n    \"\"\"Sets selection for QTableView. Creates a set of selected documents.\"\"\"\n\n    def select(self, selection, flags):\n        # which rows have been selected\n        indexes = selection.indexes() if isinstance(selection, QItemSelection) \\\n                  else [selection]\n        # prevent crashing when deleting the connection\n        if not indexes:\n            super().select(selection, flags)\n            return\n        # indexes[0].row() == -1 indicates clicking outside of the table\n        if len(indexes) == 1 and indexes[0].row() == -1:\n            self.clear()\n            return\n        word_index = self.model().word_index\n        selected_docs = {word_index[index.row()][0] for index in indexes}\n        selected_rows = [\n            row_index for row_index, (doc_index, _) in enumerate(word_index)\n            if doc_index in selected_docs]\n        selection = QItemSelection()\n        # select all rows belonging to the selected document\n        for row in selected_rows:\n            index = self.model().index(row, 0)\n            selection.select(index, index)\n        super().select(selection, flags)\n\n\nclass ConcordanceModel(QAbstractTableModel):\n    \"\"\"A model for constructing concordances from text.\"\"\"\n\n    def __init__(self):\n        QAbstractTableModel.__init__(self)\n        self.word = None\n        self.corpus = None\n        self.tokens = None\n        self.n_tokens = None\n        self.n_types = None\n        self.indices = None\n        self.word_index = None\n        self.width = 8\n        self.colored_rows = None\n\n    def set_word(self, word):\n        self.modelAboutToBeReset.emit()\n        self.word = word\n        self._compute_word_index()\n        self.modelReset.emit()\n\n    def set_corpus(self, corpus):\n        self.modelAboutToBeReset.emit()\n        self.corpus = corpus\n        self.set_tokens()\n        self._compute_indices()\n        self._compute_word_index()\n        self.modelReset.emit()\n\n    def set_tokens(self):\n        if self.corpus is None:\n            self.tokens = None\n            return\n        tokenizer = WordPunctTokenizer()\n        self.tokens = tokenizer(self.corpus.documents)\n        self.n_tokens = sum(map(len, self.tokens))\n        self.n_types = len(set(chain.from_iterable(self.tokens)))\n\n    def set_width(self, width):\n        self.modelAboutToBeReset.emit()\n        self.width = width\n        self.modelReset.emit()\n\n    def flags(self, _):\n        return Qt.ItemIsEnabled | Qt.ItemIsSelectable\n\n    def rowCount(self, parent=QModelIndex(), *args, **kwargs):\n        return 0 if parent.isValid() or self.word_index is None else len(self.word_index)\n\n    def columnCount(self, parent=None, *args, **kwargs):\n        return 3\n\n    def data(self, index, role=Qt.DisplayRole):\n        row, col = index.row(), index.column()\n        doc, index = self.word_index[row]\n\n        if role == Qt.DisplayRole:\n            tokens = self.tokens\n            if col == 0:\n                return ' '.join(tokens[doc][max(index - self.width, 0):index])\n            if col == 1:\n                return tokens[doc][index]\n            if col == 2:\n                return ' '.join(tokens[doc][index + 1:index + self.width + 1])\n\n        elif role == Qt.TextAlignmentRole:\n            return [Qt.AlignRight | Qt.AlignVCenter,\n                    Qt.AlignCenter,\n                    Qt.AlignLeft | Qt.AlignVCenter][col]\n\n        elif role == Qt.BackgroundRole:\n            const = self.word_index[row][0] in self.colored_rows\n            return QColor(236 + 19 * const, 243 + 12 * const, 255)\n\n    def _compute_indices(self):  # type: () -> Optional[None, list]\n        if self.corpus is None:\n            self.indices = None\n            return\n        self.indices = [ConcordanceIndex(doc, key=lambda x: x.lower())\n                        for doc in self.tokens]\n\n    def _compute_word_index(self):\n        if self.indices is None or self.word is None:\n            self.word_index = self.colored_rows = None\n        else:\n            self.word_index = [\n                (doc_idx, offset) for doc_idx, doc in enumerate(self.indices)\n                for offset in doc.offsets(self.word)]\n            self.colored_rows = set(sorted({d[0] for d in self.word_index})[::2])\n\n    def matching_docs(self):\n        if self.indices and self.word:\n            return sum(bool(doc.offsets(self.word)) for doc in self.indices)\n        else:\n            return 0\n\n\nclass OWConcordance(OWWidget):\n    name = \"Concordance\"\n    description = \"Display the context of the word.\"\n    icon = \"icons/Concordance.svg\"\n    priority = 520\n\n    class Inputs:\n        corpus = Input(\"Corpus\", Corpus)\n        query_word = Input(\"Query Word\", Topic)\n\n    class Outputs:\n        selected_documents = Output(\"Selected Documents\", Corpus)\n\n    settingsHandler = PerfectDomainContextHandler(\n        match_values = PerfectDomainContextHandler.MATCH_VALUES_ALL\n    )\n    autocommit = Setting(True)\n    context_width = Setting(5)\n    word = ContextSetting(\"\", exclude_metas=False)\n    selected_rows = Setting([], schema_only=True)\n\n    class Warning(OWWidget.Warning):\n        multiple_words_on_input = Msg(\"Multiple query words on input. \"\n                                      \"Only the first one is considered!\")\n\n    def __init__(self):\n        super().__init__()\n\n        self.corpus = None      # Corpus\n        self.n_matching = ''    # Info on docs matching the word\n        self.n_tokens = ''      # Info on tokens\n        self.n_types = ''       # Info on types (unique tokens)\n        self.is_word_on_input = False\n\n        # Info attributes\n        info_box = gui.widgetBox(self.controlArea, 'Info')\n        gui.label(info_box, self, 'Tokens: %(n_tokens)s')\n        gui.label(info_box, self, 'Types: %(n_types)s')\n        gui.label(info_box, self, 'Matching: %(n_matching)s')\n\n        # Width parameter\n        gui.spin(self.controlArea, self, 'context_width', 3, 10, box=True,\n                 label=\"Number of words:\", callback=self.set_width)\n\n        gui.rubber(self.controlArea)\n\n        # Search\n        c_box = gui.widgetBox(self.mainArea, orientation=\"vertical\")\n        self.input = gui.lineEdit(\n            c_box, self, 'word', orientation=Qt.Horizontal,\n            sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding,\n                                   QSizePolicy.Fixed),\n            label='Query:', callback=self.set_word, callbackOnType=True)\n        self.input.setFocus()\n\n        # Concordances view\n        self.conc_view = QTableView()\n        self.model = ConcordanceModel()\n        self.conc_view.setModel(self.model)\n        self.conc_view.setWordWrap(False)\n        self.conc_view.setSelectionBehavior(QTableView.SelectRows)\n        self.conc_view.setSelectionModel(DocumentSelectionModel(self.model))\n        self.conc_view.setItemDelegate(HorizontalGridDelegate())\n        self.conc_view.selectionModel().selectionChanged.connect(self.selection_changed)\n        self.conc_view.horizontalHeader().hide()\n        self.conc_view.setShowGrid(False)\n        self.mainArea.layout().addWidget(self.conc_view)\n        self.set_width()\n\n        # Auto-commit box\n        gui.auto_commit(self.controlArea, self, 'autocommit', 'Commit',\n                        'Auto commit is on')\n\n    def sizeHint(self): # pragma: no cover\n        return QSize(600, 400)\n\n    def set_width(self):\n        sel = self.conc_view.selectionModel().selection()\n        self.model.set_width(self.context_width)\n        if sel:\n            self.conc_view.selectionModel().select(sel,\n                QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows)\n\n    def selection_changed(self):\n        selection = self.conc_view.selectionModel().selection()\n        self.selected_rows = sorted(set(cell.row() for cell in selection.indexes()))\n        self.commit()\n\n    def set_selection(self, selection):\n        if selection:\n            sel = QItemSelection()\n            for row in selection:\n                index = self.conc_view.model().index(row, 0)\n                sel.select(index, index)\n            self.conc_view.selectionModel().select(sel,\n                QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows)\n\n    @Inputs.corpus\n    def set_corpus(self, data=None):\n        self.closeContext()\n        self.corpus = data\n        if data is None:    # data removed, clear selection\n            self.selected_rows = []\n\n        if not self.is_word_on_input:\n            self.word = \"\"\n            self.openContext(self.corpus)\n\n        self.model.set_corpus(self.corpus)\n        self.set_word()\n\n    @Inputs.query_word\n    def set_word_from_input(self, topic):\n        self.Warning.multiple_words_on_input.clear()\n        if self.is_word_on_input:   # word changed, clear selection\n            self.selected_rows = []\n        self.is_word_on_input = topic is not None and len(topic) > 0\n        self.input.setEnabled(not self.is_word_on_input)\n        if self.is_word_on_input:\n            if len(topic) > 1:\n                self.Warning.multiple_words_on_input()\n            self.word = topic.metas[0, 0]\n            self.set_word()\n\n    def set_word(self):\n        self.selected_rows = []\n        self.model.set_word(self.word)\n        self.update_widget()\n        self.commit()\n\n    def handleNewSignals(self):\n        self.set_selection(self.selected_rows)\n\n    def resize_columns(self):\n        col_width = (self.conc_view.width() -\n                     self.conc_view.columnWidth(1)) / 2 - 12\n        self.conc_view.setColumnWidth(0, col_width)\n        self.conc_view.setColumnWidth(2, col_width)\n\n    def resizeEvent(self, event): # pragma: no cover\n        super().resizeEvent(event)\n        self.resize_columns()\n\n    def update_widget(self):\n        self.conc_view.resizeColumnToContents(1)\n        self.resize_columns()\n        self.conc_view.resizeRowsToContents()\n\n        if self.corpus is not None:\n            self.n_matching = '{}/{}'.format(\n                self.model.matching_docs() if self.word else 0,\n                len(self.corpus))\n            self.n_tokens = self.model.n_tokens\n            self.n_types = self.model.n_types\n        else:\n            self.n_matching = ''\n            self.n_tokens = ''\n            self.n_types = ''\n\n    def commit(self):\n        selected_docs = sorted(set(self.model.word_index[row][0]\n                                   for row in self.selected_rows))\n        if selected_docs:\n            selected = self.corpus[selected_docs]\n            self.Outputs.selected_documents.send(selected)\n        else:\n            self.Outputs.selected_documents.send(None)\n\n    def send_report(self):\n        view = self.conc_view\n        model = self.conc_view.model()\n        self.report_items(\"Concordances\", (\n            (\"Query\", model.word),\n            (\"Tokens\", model.n_tokens),\n            (\"Types\", model.n_types),\n            (\"Matching\", self.n_matching),\n        ))\n        self.report_table(view)\n\n\nif __name__ == '__main__': # pragma: no cover\n    app = QApplication([])\n    widget = OWConcordance()\n    corpus = Corpus.from_file('book-excerpts')\n    corpus = corpus[:3]\n    widget.set_corpus(corpus)\n    widget.show()\n    app.exec()\n\n", "path": "orangecontrib/text/widgets/owconcordance.py"}]}