Skip to content

Commit cf91510

Browse files
authored
Merge pull request #28 from mfat/testing
Testing
2 parents 37ab490 + 80ca9cb commit cf91510

File tree

8 files changed

+287
-84
lines changed

8 files changed

+287
-84
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ src/.DS_Store
2020
/.flatpak-builder
2121
/repo
2222
io.github.mfat.jottr-1.4.1.flatpak
23+
/deb_dist
24+
/deb_dist

packaging/debian/changelog

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
jottr (1.4.2-1) unstable; urgency=medium
1+
jottr (1.4.3-1) unstable; urgency=medium
22

33
* Bug fixes
44
* Updated icon
55

6-
-- mFat <[email protected]> Sat, 22 Feb 2025 12:00:00 +0000
6+
-- mFat <[email protected]> Sat, 01 Mar 2025 12:00:00 +0000

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ pyenchant>=3.2.0
1313
feedparser>=6.0.0
1414
requests==2.31.0
1515
pyinstaller>=4.5.1
16-
dmgbuild>=1.4.2
16+
dmgbuild>=1.4.2
17+
pyspellchecker>=0.7.2

rpm.spec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Name: jottr
2-
Version: 1.4.2
2+
Version: 1.4.3
33
Release: 1%{?dist}
44
Summary: A simple text editor for writers, journalists and researchers
55

@@ -78,6 +78,6 @@ install -p -m 644 icons/jottr.png %{buildroot}%{_datadir}/icons/hicolor/256x256/
7878
%{_datadir}/icons/hicolor/256x256/apps/%{name}.png
7979

8080
%changelog
81-
* Sat Feb 22 2025 mFat <mfat@github.com> - 1.4.2-1
81+
* Sat Mar 01 2025 mFat <newmfat@gmail.com> - 1.4.3-1
8282
- Bug fixes and improvements
8383

src/jottr/editor_tab.py

Lines changed: 143 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -13,37 +13,100 @@
1313
from PyQt5.QtGui import (QTextCharFormat, QSyntaxHighlighter, QIcon, QFont, QKeySequence,
1414
QPainter, QPen, QColor, QFontMetrics, QTextDocument, QTextCursor)
1515
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
16-
from enchant import Dict, DictNotFoundError
1716
from urllib.parse import quote
1817
from snippet_editor_dialog import SnippetEditorDialog
19-
import os
2018
from rss_reader import RSSReader
2119
import json
2220
import time
2321
from theme_manager import ThemeManager
2422
import hashlib
2523

24+
# Try enchant first, fallback to pyspellchecker
25+
try:
26+
from enchant import Dict, DictNotFoundError
27+
USE_ENCHANT = True
28+
except (ImportError, ModuleNotFoundError) as e:
29+
print("Enchant not available, falling back to pyspellchecker:", str(e))
30+
from spellchecker import SpellChecker
31+
USE_ENCHANT = False
32+
2633
class SpellCheckHighlighter(QSyntaxHighlighter):
2734
def __init__(self, parent, settings_manager):
2835
super().__init__(parent)
2936
self.settings_manager = settings_manager
37+
self.spell_check_enabled = True
38+
self.USE_ENCHANT = USE_ENCHANT # Store the global flag
39+
3040
try:
31-
# Initialize enchant with English dictionary
32-
self.spell = Dict("en_US")
33-
self.spell_check_enabled = True
34-
print("Spell checking enabled")
35-
36-
except DictNotFoundError as e:
37-
print(f"Warning: Spell checking disabled - {str(e)}")
38-
self.spell_check_enabled = False
41+
if self.USE_ENCHANT:
42+
self.spell = Dict("en_US")
43+
print("Using Enchant for spell checking")
44+
else:
45+
self.spell = SpellChecker()
46+
print("Using pyspellchecker for spell checking")
47+
except Exception as e:
48+
print(f"Spell checker initialization error: {str(e)}, falling back to pyspellchecker")
49+
self.spell = SpellChecker()
50+
self.USE_ENCHANT = False
3951

40-
def is_latin_word(self, word):
41-
"""Check if word contains only Latin characters"""
42-
try:
43-
word.encode('latin-1')
52+
def check_word(self, word):
53+
"""Check if a word is spelled correctly"""
54+
if not self.spell_check_enabled:
4455
return True
45-
except UnicodeEncodeError:
46-
return False
56+
57+
if self.USE_ENCHANT:
58+
return self.spell.check(word)
59+
else:
60+
# pyspellchecker considers unknown words misspelled
61+
return word.lower() in self.spell
62+
63+
def suggest(self, word):
64+
"""Get suggestions for a word"""
65+
if not self.spell_check_enabled:
66+
return []
67+
68+
# Get user dictionary
69+
user_dict = self.settings_manager.get_setting('user_dictionary', [])
70+
71+
# Add matching words from user dictionary first
72+
suggestions = [dict_word for dict_word in user_dict
73+
if dict_word.lower().startswith(word.lower())]
74+
75+
# Only get spell checker suggestions for Latin words
76+
if self.is_latin_word(word):
77+
try:
78+
if self.USE_ENCHANT:
79+
spell_suggestions = self.spell.suggest(word)
80+
else:
81+
spell_suggestions = self.spell.candidates(word)
82+
83+
if spell_suggestions:
84+
# Remove the word itself from suggestions
85+
spell_suggestions = [s for s in spell_suggestions
86+
if s.lower() != word.lower()]
87+
suggestions.extend(spell_suggestions)
88+
except UnicodeEncodeError:
89+
pass
90+
91+
# Remove duplicates while preserving order
92+
return list(dict.fromkeys(suggestions))
93+
94+
def add_to_dictionary(self, word):
95+
"""Add word to user dictionary"""
96+
if self.USE_ENCHANT:
97+
# Enchant spell checker implementation
98+
self.spell.add(word)
99+
else:
100+
# PySpellChecker implementation
101+
self.spell.word_frequency.add(word)
102+
# Force a recheck of the document
103+
self.highlighter.rehighlight()
104+
105+
# Add to user dictionary in settings
106+
user_dict = self.settings_manager.get_setting('user_dictionary', [])
107+
if word not in user_dict:
108+
user_dict.append(word)
109+
self.settings_manager.save_setting('user_dictionary', user_dict)
47110

48111
def highlightBlock(self, text):
49112
if not self.spell_check_enabled:
@@ -67,52 +130,21 @@ def highlightBlock(self, text):
67130
if self.is_latin_word(word):
68131
# Check if word is in user dictionary first
69132
if word not in user_dict:
70-
# If not in user dictionary, check against enchant
71133
try:
72-
if not self.spell.check(word):
134+
if not self.check_word(word):
73135
self.setFormat(index, length, format)
74136
except UnicodeEncodeError:
75137
pass # Skip words that can't be encoded
76138

77139
index = expression.indexIn(text, index + length)
78140

79-
def suggest(self, word):
80-
"""Get suggestions for a word, including user dictionary matches"""
81-
suggestions = []
82-
83-
# Get user dictionary
84-
user_dict = self.settings_manager.get_setting('user_dictionary', [])
85-
86-
# Add matching words from user dictionary first
87-
for dict_word in user_dict:
88-
if dict_word.lower().startswith(word.lower()):
89-
suggestions.append(dict_word)
90-
91-
# Only get enchant suggestions for Latin words
92-
if self.is_latin_word(word):
93-
try:
94-
# Get suggestions from enchant
95-
spell_suggestions = self.spell.suggest(word)
96-
if spell_suggestions:
97-
# Remove the word itself from suggestions
98-
spell_suggestions = [s for s in spell_suggestions if s.lower() != word.lower()]
99-
suggestions.extend(spell_suggestions)
100-
except UnicodeEncodeError:
101-
pass
102-
103-
# Remove duplicates while preserving order
104-
return list(dict.fromkeys(suggestions))
105-
106-
def add_to_dictionary(self, word):
107-
"""Add word to user dictionary"""
108-
user_dict = self.settings_manager.get_setting('user_dictionary', [])
109-
if word not in user_dict:
110-
user_dict.append(word)
111-
self.settings_manager.save_setting('user_dictionary', user_dict)
112-
# Add to enchant personal word list
113-
self.spell.add(word)
114-
# Refresh spell checking
115-
self.rehighlight()
141+
def is_latin_word(self, word):
142+
"""Check if word contains only Latin characters"""
143+
try:
144+
word.encode('latin-1')
145+
return True
146+
except UnicodeEncodeError:
147+
return False
116148

117149
class CustomTextEdit(QTextEdit):
118150
def __init__(self, parent=None):
@@ -181,6 +213,16 @@ def __init__(self, parent=None):
181213
self.completion_text = ""
182214
self.completion_start = None
183215
self.suppress_completion = False
216+
217+
# Initialize spell checker
218+
if USE_ENCHANT:
219+
try:
220+
self.spell_checker = Dict("en_US")
221+
except:
222+
self.spell_checker = SpellChecker()
223+
print("Fallback to pyspellchecker in CompletingTextEdit")
224+
else:
225+
self.spell_checker = SpellChecker()
184226

185227
def keyPressEvent(self, event):
186228
"""Handle key events"""
@@ -292,13 +334,19 @@ def __init__(self, snippet_manager, settings_manager):
292334
self.web_view = None # Initialize to None
293335
self.main_window = None # Initialize main_window to None
294336

295-
# # Initialize recovery ID and paths first
296-
# self.recovery_id = str(int(time.time() * 1000))
297-
# self.session_path = os.path.join(
298-
# self.settings_manager.get_recovery_dir(),
299-
# f"session_{self.recovery_id}.txt"
300-
# )
301-
# self.meta_path = self.session_path + '.json'
337+
# Add USE_ENCHANT as instance attribute
338+
self.USE_ENCHANT = USE_ENCHANT # Use the module-level variable
339+
340+
# Initialize spell checker
341+
if self.USE_ENCHANT:
342+
try:
343+
self.spell_checker = Dict("en_US")
344+
except:
345+
self.USE_ENCHANT = False # Fall back if enchant fails
346+
self.spell_checker = SpellChecker()
347+
print("Fallback to pyspellchecker in EditorTab")
348+
else:
349+
self.spell_checker = SpellChecker()
302350

303351
# Setup UI components
304352
self.setup_ui()
@@ -688,21 +736,24 @@ def show_context_menu(self, pos):
688736
cursor.select(cursor.WordUnderCursor)
689737
self.editor.setTextCursor(cursor)
690738

739+
# Create menu with a slight delay to prevent accidental triggers
740+
QTimer.singleShot(100, lambda: self._show_context_menu_impl(pos))
741+
742+
def _show_context_menu_impl(self, pos):
743+
"""Implementation of context menu display"""
691744
menu = QMenu(self)
692745

693-
# Cut/Copy/Paste actions
694-
menu.addAction("Cut", self.editor.cut)
695-
menu.addAction("Copy", self.editor.copy)
696-
menu.addAction("Paste", self.editor.paste)
697-
menu.addSeparator()
746+
# # Cut/Copy/Paste actions
747+
# menu.addAction("Cut", self.editor.cut)
748+
# menu.addAction("Copy", self.editor.copy)
749+
# menu.addAction("Paste", self.editor.paste)
750+
# menu.addSeparator()
698751

699752
# Get selected text
700-
selected_text = cursor.selectedText()
753+
selected_text = self.editor.textCursor().selectedText()
701754

702755
if selected_text:
703-
# Add "Save as Snippet" option
704-
menu.addAction("Save as Snippet", lambda: self.save_snippet(selected_text))
705-
menu.addSeparator()
756+
706757

707758
# Add search submenu
708759
search_menu = menu.addMenu("Search in...")
@@ -783,6 +834,14 @@ def show_context_menu(self, pos):
783834
add_action = menu.addAction("Add to Dictionary")
784835
add_action.triggered.connect(lambda: self.add_to_dictionary(selected_text))
785836
menu.addSeparator()
837+
# Add "Save as Snippet" option
838+
menu.addAction("Save as Snippet", lambda: self.save_snippet(selected_text))
839+
menu.addSeparator()
840+
# Cut/Copy/Paste actions
841+
menu.addAction("Cut", self.editor.cut)
842+
menu.addAction("Copy", self.editor.copy)
843+
menu.addAction("Paste", self.editor.paste)
844+
menu.addSeparator()
786845

787846
# Show menu
788847
menu.exec_(self.editor.mapToGlobal(pos))
@@ -819,14 +878,20 @@ def search_in_browser(self, url):
819878

820879
def add_to_dictionary(self, word):
821880
"""Add word to user dictionary"""
881+
if self.USE_ENCHANT:
882+
# Enchant spell checker implementation
883+
self.spell_checker.add(word)
884+
else:
885+
# PySpellChecker implementation
886+
self.spell_checker.word_frequency.add(word)
887+
# Force a recheck of the document
888+
self.highlighter.rehighlight()
889+
890+
# Add to user dictionary in settings
822891
user_dict = self.settings_manager.get_setting('user_dictionary', [])
823892
if word not in user_dict:
824893
user_dict.append(word)
825894
self.settings_manager.save_setting('user_dictionary', user_dict)
826-
# Add to enchant personal word list
827-
self.highlighter.spell.add(word)
828-
# Refresh spell checking
829-
self.highlighter.rehighlight()
830895

831896
def ensure_browser_visible(self):
832897
"""Ensure browser pane is visible"""
@@ -1565,9 +1630,9 @@ def set_main_window(self, main_window):
15651630
self.main_window = main_window
15661631
# # Update session state to include this tab
15671632
# current_tabs = self.main_window.get_open_tab_ids()
1568-
# if self.recovery_id not in current_tabs:
1569-
# current_tabs.append(self.recovery_id)
1570-
# self.settings_manager.save_session_state(current_tabs)
1633+
# # if self.recovery_id not in current_tabs:
1634+
# # current_tabs.append(self.recovery_id)
1635+
# # self.settings_manager.save_session_state(current_tabs)
15711636

15721637
# def cleanup_session_files(self):
15731638
# """Clean up session files for this tab"""

0 commit comments

Comments
 (0)