Script: typing_counter.py

Bar item showing typing count and cursor position.
Author: fauno — Version: 1.0 — License: GPL-3.0-or-later
For WeeChat ≥ 0.3.2.
Tags: input, item, py2, py3
Added: 2010-03-05 — Updated: 2018-11-04

Download GitHub Repository

  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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
# Copyright (c) 2010-2013 by fauno <fauno@kiwwwi.com.ar>
# Copyright (c) 2018 by nils_2 <freenode.#weechat>
#
# Bar item showing typing count. Add 'tc' to a bar.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# 0.1:  initial release
# 0.2 <nils_2@freenode>:
#       fixed display bug when buffer changes
#       added cursor position
#       colour of number changes if a specified number of chars is reached
#       added reverse counting
# 0.2.2 <nils_2@freenode>:
#       update settings instantly when changed
#       fix display bug when loading the script first time
# 0.2.3 <nils_2@freenode>:
#       fix display bug with count_over. Wasn't set to 0
# 0.3 <nils_2@freenode>:
#       added sound-alarm when cursor position is -1 or higher than 'max_chars'
#       improved option-handling
# 0.4 <nils_2@freenode>:
#       fix display bug with more than one window
# 0.5 <nils_2@freenode>:
#       add description for options
#       add tweet and sms counter for bitlbee and gtalksms (suggested by ahuemer@freenode)
# 0.6 <nils_2@freenode>:
#       add support for gtalksms "reply" (suggested by ahuemer@freenode)
# 0.7 <nils_2@freenode>:
#       fix bug with root bar (reported by fours_)
# 0.8 <nils_2@freenode>:
#       fix regex bug with ":" in sms text (reported by ahuemer)
# 0.9 <nils_2@freenode>:
#       add option 'start_cursor_pos_at_zero' (idea by nesthib)
# 1.0 <nils_2@freenode>:
#       make script python3 compatible
#       add regular expression for format option
#
# usage:
# add [tc] to your weechat.bar.status.items
#
# config:
# %P = cursor position
# %L = input length
# %R = reverse counting from max_chars
# %C = displays how many chars are count over max_chars
# /set plugins.var.python.typing_counter.format "[%P|%L|<%R|%C>]"
#
# color for warn after specified number of chars
# /set plugins.var.python.typing_counter.warn_colour "red"
#
# turns indicator to "warn_colour" when position is reached
# /set plugins.var.python.typing_counter.warn "150"
#
# max number of chars to count reverse
# /set plugins.var.python.typing_counter.max_chars "200"
#
# to activate a display beep use.
# /set plugins.var.python.typing_counter.warn_command "$bell"
#
# TODO:
# - buffer whitelist/blacklist
# - max chars per buffer (ie, bar item will turn red when count > 140 for identica buffer)

from __future__ import print_function

SCRIPT_NAME    = "typing_counter"
SCRIPT_AUTHOR  = "fauno <fauno@kiwwwi.com.ar>"
SCRIPT_VERSION = "1.0"
SCRIPT_LICENSE = "GPL3"
SCRIPT_DESC    = "Bar item showing typing count and cursor position. Add 'tc' to a bar."

import_ok = True

try:
  import weechat as w

except Exception:
    print("This script must be run under WeeChat.")
    print("Get WeeChat now at: https://weechat.org/")
    import_ok = False
try:
    import os, sys, re

except ImportError as message:
    print(('Missing package(s) for %s: %s' % (SCRIPT_NAME, message)))
    import_ok = False

tc_input_text   = ''
length          = 0
cursor_pos      = 1
count_over      = '0'

tc_default_options = {
    'format'            : ('[%P|%L|<%R|%C>]','item name to add in a bar is "tc". item format is: %P = cursor position, %L = input length, %R = reverse counting from max_chars, %C = displays how many chars are count over max_chars (content is evaluated, eg use colors with format "${color:xxx}", see /help eval)'),
    'warn'              : ('140','turns indicator to "warn_colour" when position is reached'),
    'warn_colour'       : ('red','color for warn after specified number of chars'),
    'max_chars'         : ('200','max number of chars to count reverse'),
    'warn_command'      : ('', 'to activate a display beep use: $beep'),
    'tweet_buffer'      : ('bitlbee.#tweet','name of tweet buffer. This is a comma separated list'),
    'sms_buffer'        : ('bitlbee.sms','name of sms buffer (using gtalksms). This is a comma separated list'),
    'start_cursor_pos_at_zero': ('off','if option on, cursor position will start counting from zero instead of one'),
}
tc_options = {}

# regexp to match ${color} tags
regex_color=re.compile('\$\{([^\{\}]+)\}')

# regexp to match ${optional string} tags
regex_optional_tags=re.compile('%\{[^\{\}]+\}')

def command_run_cb (data, signal, signal_data):
    if tc_options['warn_command'] == '':
        return w.WEECHAT_RC_OK
    global length, cursor_pos, tc_input_text
    current_buffer = w.current_buffer()
    start_pos = int(tc_options['start_cursor_pos_at_zero'].lower() == 'off')
    cursor_pos = w.buffer_get_integer(current_buffer,'input_pos') + start_pos
    if (cursor_pos -1) == 0:
        tc_action_cb()
    return w.WEECHAT_RC_OK

def tc_bar_item_update (data=None, signal=None, signal_data=None):
    '''Updates bar item'''
    '''May be used as a callback or standalone call.'''
    global length, cursor_pos, tc_input_text

    w.bar_item_update('tc')
    return w.WEECHAT_RC_OK

def tc_bar_item (data, item, window):
    '''Item constructor'''
    # window empty? root bar!
    if not window:
        window = w.current_window()

    global length, cursor_pos, tc_input_text, count_over,tc_options
    count_over = '0'
    sms = ''
    tweet = ''
    reverse_chars = 0

    ptr_buffer = w.window_get_pointer(window,"buffer")
    if ptr_buffer == "":
        return ""

    length = w.buffer_get_integer(ptr_buffer,'input_length')
    start_pos = int(tc_options['start_cursor_pos_at_zero'].lower() == 'off')
    cursor_pos = w.buffer_get_integer(ptr_buffer,'input_pos') + start_pos

    plugin = w.buffer_get_string(ptr_buffer, 'plugin')

    host = ''
    if plugin == 'irc':
        servername = w.buffer_get_string(ptr_buffer, 'localvar_server')
        channelname = w.buffer_get_string(ptr_buffer, 'localvar_channel')
        channel_type = w.buffer_get_string(ptr_buffer, 'localvar_type')
        name = w.buffer_get_string(ptr_buffer, 'localvar_name')
        input_line = w.buffer_get_string(ptr_buffer, 'input')
        mynick = w.info_get('irc_nick', servername)
        nick_ptr = w.nicklist_search_nick(ptr_buffer, '', mynick)

        # check for a sms message
        if channel_type == 'private' and name in tc_options['sms_buffer'].split(","):
            # 160 chars for a sms
            # 'sms:name:text'
            get_sms_text = re.match(r'(s|sms):(.*?:)(.*)', input_line)
            if get_sms_text:
#            if get_sms_text.group(2):
                sms_len = len(get_sms_text.group(3))
#                input_length = len(input_line)
#                sms_prefix = input_length - sms_len
                sms = 160-sms_len
                reverse_chars = sms
            else:
                get_sms_text = re.match(r'(r|reply):(.*)', input_line)
                if get_sms_text:
                    sms_len = len(get_sms_text.group(2))
                    sms = 160-sms_len
                    reverse_chars = sms

        # check for a tweet buffer
        elif name in tc_options['tweet_buffer'].split(","):
            # 140 chars for a tweet! prefix "post " = 5 chars

            # check out if length >= 5 and matches "post "
            if length >= 5 and re.match(r'post (.*)', input_line):
                tweet = 145 - length
                reverse_chars = tweet

        # get host and length from host
        elif servername != channelname:
            infolist = w.infolist_get('irc_nick', '', '%s,%s,%s' % (servername,channelname,mynick))
#            w.prnt("","%s.%s.%s.%s" % (servername,channelname,mynick,nick_ptr))
            while w.infolist_next(infolist):
                host = w.infolist_string(infolist, 'host')
            w.infolist_free(infolist)
            if host != '':
                host = ':%s!%s PRIVMSG %s :' % (mynick,host,channelname)
                host_length = len(host)
#        w.prnt("","%d" % host_length)
                reverse_chars = (475 - int(host_length) - length -1)    # -1 = return
            else:
                reverse_chars = (int(tc_options['max_chars']) - length)
        else:
            reverse_chars = (int(tc_options['max_chars']) - length)
    else:
        # reverse check for max_chars
        reverse_chars = (int(tc_options['max_chars']) - length)

    if reverse_chars == 0:
        reverse_chars = "%s" % ("0")
    else:
        if reverse_chars < 0:
            count_over = "%s%s%s" % (w.color(tc_options['warn_colour']),str(reverse_chars*-1), w.color('default'))
            reverse_chars = "%s" % ("0")
            tc_action_cb()
        else:
            reverse_chars = str(reverse_chars)

    out_format = tc_options['format']
    if tc_options['warn']:
        if length >= int(tc_options['warn']):
            length_warn = "%s%s%s" % (w.color(tc_options['warn_colour']), str(length), w.color('default'))
            out_format = out_format.replace('%L', length_warn)
        else:
            out_format = out_format.replace('%L', str(length))
    else:
            out_format = out_format.replace('%L', str(length))

    out_format = out_format.replace('%P', str(cursor_pos))
    if sms:
        out_format = out_format.replace('%R', "s:" + reverse_chars)
    elif tweet:
        out_format = out_format.replace('%R', "t:" + reverse_chars)
    else:
        out_format = out_format.replace('%R', reverse_chars)
    out_format = out_format.replace('%C', count_over)
#    out_format = out_format.replace('%T', str(tweet))
#    out_format = out_format.replace('%S', str(sms))
    tc_input_text = out_format

    return substitute_colors(tc_input_text)

def substitute_colors(text):
    if int(version) >= 0x00040200:
        return w.string_eval_expression(text,{},{},{})
    # substitute colors in output
    return re.sub(regex_color, lambda match: w.color(match.group(1)), text)


def init_config():
    global tc_default_options, tc_options

    for option,value in list(tc_default_options.items()):
        w.config_set_desc_plugin(option, '%s (default: "%s")' % (value[1], value[0]))
        if not w.config_is_set_plugin(option):
            w.config_set_plugin(option, value[0])
            tc_options[option] = value[0]
        else:
            tc_options[option] = w.config_get_plugin(option)

def config_changed(data, option, value):
    init_config()
    return w.WEECHAT_RC_OK

def tc_action_cb():
    global tc_options
    if tc_options['warn_command']:
        if tc_options['warn_command'] == '$bell':
            f = open('/dev/tty', 'w')
            f.write('\a')
            f.close()
        else:
            os.system(tc_options['warn_command'])
    return w.WEECHAT_RC_OK

if __name__ == "__main__" and import_ok:
    if w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION,
                  SCRIPT_LICENSE, SCRIPT_DESC,
                  "", ""):
        version = w.info_get("version_number", "") or 0
        init_config() # read configuration
        tc_bar_item_update() # update status bar display

        w.hook_signal('input_text_changed', 'tc_bar_item_update', '')
        w.hook_signal('input_text_cursor_moved','tc_bar_item_update','')
        w.hook_command_run('/input move_previous_char','command_run_cb','')
        w.hook_command_run('/input delete_previous_char','command_run_cb','')
        w.hook_signal('buffer_switch','tc_bar_item_update','')
        w.hook_config('plugins.var.python.' + SCRIPT_NAME + ".*", "config_changed", "")
        w.bar_item_new('tc', 'tc_bar_item', '')