/
ReplaceDefaultReplaceDialog.py
144 lines (117 loc) · 6.26 KB
/
ReplaceDefaultReplaceDialog.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# -*- coding: utf-8 -*-
"""
ReplaceDefaultReplaceDialog
The built-in replace dialog doesn't allow to have a
multiline replacement value.
This script solves this missing feature.
Multi-Edit needs to be activated in the N++ edit preferences.
In order to achieve this goal using WinAPI (ctypes) is needed.
What happens is basically the following:
- if two selections are made
- call built-in dialogue
- find needed window control handles
- replace the content in those controls with the multiline version of text
- else, just call built-in dialogue
Usage:
Make two selections and execute script.
Note, rectangular selections are not supported and due to
internal buffer restrictions text is truncated to 2046 if needed.
"""
from Npp import editor, notepad, BUFFERENCODING, MENUCOMMAND, SELECTIONMODE
import ctypes
from ctypes.wintypes import BOOL, HWND, LPARAM
try:
# on first run this will generate an NameError exception
REPLACE_DIALOG().show()
except NameError:
class REPLACE_DIALOG_SINGLETON(type):
''' Ensures that only one instance exists '''
_instance = None
def __call__(cls, *args, **kwargs):
''' The real constructor and first method called when
a new instance should be created.
On first instantiation class variable _instance gets itself assigned,
every subsequent instantiation try returns this object
'''
if cls._instance is None:
cls._instance = super(REPLACE_DIALOG_SINGLETON, cls).__call__(*args, **kwargs)
return cls._instance
class REPLACE_DIALOG(object):
''' Extend default replace dialog with multiline replace feature. '''
__metaclass__ = REPLACE_DIALOG_SINGLETON
def __init__(self):
''' defines the needed WinAPI functions
as well as the replace and findwhat lables,
which might be adapted if a localized version is used
'''
self.WNDENUMPROC = ctypes.WINFUNCTYPE(BOOL, HWND, LPARAM)
self.FindWindow = ctypes.windll.user32.FindWindowW
self.EnumChildWindows = ctypes.windll.user32.EnumChildWindows
self.GetClassName = ctypes.windll.user32.GetClassNameW
self.GetWindowText = ctypes.windll.user32.GetWindowTextW
self.GetWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW
self.SetWindowText = ctypes.windll.user32.SetWindowTextW
self.GetACP = ctypes.windll.kernel32.GetACP
self.create_unicode_buffer = ctypes.create_unicode_buffer
self.replace_tab_caption = u'Replace'
self.replacewith_caption = u'Rep&lace with :'
self.replacewith_is_next_ctrl = False
self.replacewith_handle = None
self.findwhat_caption = u'&Find what :'
self.findwhat_is_next_ctrl = False
self.findwhat_handle = None
def return_proper_string(self, selected_text):
if notepad.getEncoding() == BUFFERENCODING.ENC8BIT:
_selected_text = unicode(selected_text, '{}'.format(self.GetACP()))
else:
_selected_text = selected_text
return _selected_text
def enum_callback(self, hwnd, lparam):
curr_class = self.create_unicode_buffer(256)
self.GetClassName(hwnd, curr_class, 256)
length = self.GetWindowTextLength(hwnd)
buff = self.create_unicode_buffer(length + 1)
self.GetWindowText(hwnd, buff, length + 1)
if curr_class.value.lower() == 'static':
if buff.value == self.replacewith_caption:
self.replacewith_is_next_ctrl = True
elif buff.value == self.findwhat_caption:
self.findwhat_is_next_ctrl = True
elif curr_class.value.lower() == 'edit':
if self.findwhat_is_next_ctrl:
self.findwhat_handle = hwnd
self.findwhat_is_next_ctrl = False
elif self.replacewith_is_next_ctrl:
self.replacewith_handle = hwnd
self.replacewith_is_next_ctrl = False
return False # stop enumeration
return True # let enumeration continue
def find_controls(self):
find_dialog_hwnd = self.FindWindow(None, self.replace_tab_caption)
if find_dialog_hwnd:
self.EnumChildWindows(find_dialog_hwnd, self.WNDENUMPROC(self.enum_callback), 0)
else:
notepad.messageBox('Error: Bad value for find_dialog_hwnd')
def show(self):
msg = ''
notepad.menuCommand(MENUCOMMAND.SEARCH_REPLACE)
if (self.findwhat_handle is None) or (self.replacewith_handle is None):
self.find_controls()
if editor.getSelections() == 2:
if (editor.getSelectionMode() == SELECTIONMODE.STREAM) and (not editor.getSelectionEmpty()):
_find_what = editor.getTextRange(editor.getSelectionNStart(0), editor.getSelectionNEnd(0))
_replace_with = editor.getTextRange(editor.getSelectionNStart(1), editor.getSelectionNEnd(1))
if len(_find_what) > 2046:
_find_what = _find_what[:2046]
msg += 'Warning: Selected text too long for find-what box; truncating to 2046 characters.\n'
if len(_replace_with) > 2046:
_replace_with = _replace_with[:2046]
msg += 'Warning: Selected text too long for replace-with box; truncating to 2046 characters.'
if self.SetWindowText(self.findwhat_handle, self.return_proper_string(_find_what)) == 0:
msg += 'Error: Problem setting find-what text\n'
if self.SetWindowText(self.replacewith_handle, self.return_proper_string(_replace_with)) == 0:
msg += 'Error: Problem setting replace-with text'
else:
msg = 'Error: Either empty selection or unsupported selection mode detected'
if len(msg) > 0: notepad.messageBox(msg)
REPLACE_DIALOG().show()