
    fD                    (   d dl mZmZmZ d dlZd dlZd dlZd dlZd dlZd dl	m
Z
  e
        d dlmZmZ d dlZ ej                  e      Zd dlmZ d dlmZ d dlmZmZ d dlmZmZ d dlmZ d d	lmZ d d
l m!Z! d dl"m#Z# d dl$m%Z%m&Z& d dl'm(Z(m)Z)m*Z* d dl+m,Z,m-Z-m.Z. d dl/m0Z0m1Z1 d dl2m3Z3 d dl4m5Z5  e5       Z6 G d d      Z7 G d d      Z8 G d de8      Z9 G d d      Z: G d de:      Z; G d de:      Z< G d d      Z= G d  d!e!      Z> G d" d#      Z?y)$    )divisionprint_functionunicode_literalsN)require_gi_versions)GtkPango)	KeyCommon)AtspiTextContext	InputLine)TextChangesTextSpan)SpellChecker)
LanguageDB)LayoutPanel)AtspiStateTracker)WPLocalEngine
ModelCache)Rectunicode_strescape_markup)CallOnceTimer	TimerOnce)FullSizeKeyWordKey)PendingSeparatorPopup)Configc                      e Zd ZdZd Zd Zd Zd Zd Zd Z	d Z
d	 Zd
 Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Z d Z!d  Z"d! Z#d" Z$d# Z%d$ Z&d% Z'd& Z(d' Z)d( Z*d) Z+d* Z,d+ Z-d, Z.d- Z/d\d.Z0d/ Z1d0 Z2d1 Z3d2 Z4d3 Z5d4 Z6e7d]d6       Z8d7 Z9e7d8        Z:d9 Z;d: Z<d; Z=d< Z>d= Z?d> Z@d? ZAd@ ZBdA ZCdB ZDdC ZEdD ZFdE ZGdF ZHdG ZIdH ZJd^dIZKdJ ZLdK ZMdL ZNdM ZOdN ZPdO ZQdP ZRdQ ZSdR ZTdS ZUdT ZVdU ZWdV ZX eYj                  dW      Z[ eYj                  dX      Z\dY Z]dZ Z^e7d[        Z_y5)_WordSuggestionsz% Keyboard mix-in for word prediction c                    t               | _        t        |       | _        | j                  | _        t        |       | _        t        |       | _        t        | j                        | _
        t        |       | _        t               | _        t               | _        t#        |       | _        d| _        d | _        g | _        d | _        g | _        g | _        d | _        d| _        g | _        g | _        d| _        y NFr   )r   
input_liner
   atspi_text_contexttext_contextLearnStrategyLRU_learn_strategyr   _languagedbr   _spell_checkerPunctuatorImmediateSeparators_punctuatorr   _pending_separator_popupr   _pending_separator_popup_timerModelErrorRecovery_load_error_recovery_load_errors_reported	_wpengine_correction_choices_correction_span_prediction_choices
word_infos_separator_before_key_press_hide_input_line_word_list_bars_text_displays_focusable_countselfs    9/usr/lib/python3/dist-packages/Onboard/WordSuggestions.py__init__zWordSuggestions.__init__9   s    #+"24"8 33/5%d+*4+;+;<8>(=(?%.3g+$6t$<!%*"#%  $#% +/( %!  !    c                 $    | j                          y N)commit_changesr:   s    r<   resetzWordSuggestions.resetU   s    r>   c                     | j                          | j                  r| j                  j                          | j                  r| j                  j                          y y r@   )rB   r$   cleanupr0   r:   s    r<   rD   zWordSuggestions.cleanupX   sB    

%%'>>NN""$ r>   c                     | j                  t        f      | _        | j                  d      | _        | j                  t        j                                y )N)	inputline)find_items_from_classesWordListPanelr7   find_items_from_idsr8   enable_word_suggestionsconfigare_word_suggestions_enabledr:   s    r<   on_layout_loadedz WordSuggestions.on_layout_loaded_   sB    #;;]<LM"66~F$$V%H%H%JKr>   c                 x   | j                         D ]	  }||_         | j                         D ]	  }||_         | j                  }|rD|j	                         D ]1  }|j
                  dk(  r||_        |j
                  dk(  s*| |_        3 | j                          | j                          | j                          y )Nwordlist
nowordlist)	_get_wordlist_barsvisibleget_text_displayslayoutiter_global_itemsgroup_update_wp_engine_update_spell_checker_update_punctuator)r;   enableitemrT   s       r<   rJ   z'WordSuggestions.enable_word_suggestionsd   s     ++- 	"D!DL	"**, 	"D!DL	" 002 .::+#)DLZZ</'-:DL	. 	 ""$!r>   c                 B   t         j                         }|r0| j                         rMt               | _        | j                          n-| j                  r| j                  j                          d | _        | j                  | _        | j                  j                  |       y r@   )
rK   is_typing_assistance_enabledrQ   r   r0   apply_prediction_profilerD   r#   r$   rZ   )r;   rZ   s     r<   rW   z!WordSuggestions._update_wp_enginey   sv    446&&(!.--/~~&&(!DN
 !33  (r>   c                     | j                  t        j                                | j                          | j	                          y)z7 Config callback for word_suggestions.enabled changes. N)rJ   rK   rL   invalidate_uicommit_ui_updates)r;   enableds     r<   on_word_suggestions_enabledz+WordSuggestions.on_word_suggestions_enabled   s1    $$V%H%H%JK r>   c           	         | j                   r| j                         }| j                  j                  |      }d|z   g}d|z   g}dg}||z   }|}t        j                  dj                  t        |      t        |      t        |                   |D ]  }||vsd }t        j                  d        n | j                   j                  |||       t        d| j                         y y )Nz
lm:system:zlm:user:zlm:memz:selecting language models: system={} user={} auto_learn={}z:No auto learn model selected. Please setup learning first.   )r0   get_lang_idr'   find_system_model_language_id_loggerinfoformatreprwarning
set_modelsr   _load_models)	r;   lang_idsystem_lang_idsystem_modelsuser_modelsscratch_modelspersistent_modelsauto_learn_modelsmodels	            r<   r^   z(WordSuggestions.apply_prediction_profile   s   >>&&(G  >>wG  +^;<M(723K&ZN - ; +LL ; &m!4!%k!2!%&7!8:; + +(,%OO %C D NN%%&7&7&46 a**+E r>   c                     | j                   j                          | j                  s-d| _        | j                  j	                  | j                          y y )NT)r0   load_modelsr/   r.   report_errorsr:   s    r<   rn   zWordSuggestions._load_models   s@    ""$)))-D&%%33DNNC *r>   c                 8    | j                   j                  d      S )% Union of all system and user models system)r0   get_model_namesr:   s    r<   get_system_model_namesz&WordSuggestions.get_system_model_names   s    ~~--h77r>   c                     g }| j                   rbt        | j                   j                  d            }t        | j                   j                  d            }t        |j	                  |            }|S )r{   r|   user)r0   setr}   listunion)r;   namesrq   rr   s       r<   get_merged_model_namesz&WordSuggestions.get_merged_model_names   sZ    >> > >x HIMdnn<<VDEK,,[9:Er>   c                 R    | j                         }|st        j                         }|S )z2
        Current language id; never None.
        )get_active_lang_idrK   get_system_default_lang_idr;   ro   s     r<   rf   zWordSuggestions.get_lang_id   s(     ))+779Gr>   c                 6    t         j                  j                  S )zH
        Current language id; None for system default language.
        )rK   typing_assistanceactive_languager:   s    r<   r   z"WordSuggestions.get_active_lang_id   s     ''777r>   c                     | j                          | j                          | j                          | j                          y r@   )rX   r^   invalidate_context_uira   r:   s    r<   on_active_lang_id_changedz)WordSuggestions.on_active_lang_id_changed   s2    ""$%%'""$ r>   c                 N    |t         j                  _        | j                          y r@   )rK   r   r   r   r   s     r<   set_active_lang_idz"WordSuggestions.set_active_lang_id   s    3:  0&&(r>   c                     | j                   S )zd
        Return all word list bars, so we don't have
        to look for them all the time.
        )r7   r:   s    r<   rQ   z"WordSuggestions._get_wordlist_bars   s    
 ###r>   c                     | j                   S )zi
        Return all text feedback items, so we don't have
        to look for them all the time.
        )r8   r:   s    r<   rS   z!WordSuggestions.get_text_displays   s    
 """r>   c                 6    | j                   j                         S r@   )r(   get_supported_dict_idsr:   s    r<   get_spellchecker_dictsz&WordSuggestions.get_spellchecker_dicts   s    ""99;;r>   c                 r    | j                          | j                  r| j                  j                          y y r@   )on_activity_detectedr0   pause_autosaver:   s    r<   on_any_key_downzWordSuggestions.on_any_key_down   s,    !!# >>NN))+ r>   c                 R    | j                   r| j                   j                          y y r@   )r0   resume_autosaver:   s    r<   on_all_keys_upzWordSuggestions.on_all_keys_up   s    >>NN**, r>   c                    | j                   j                         | _        |j                         s+|j	                         s| j
                  j                  |       | j                   j                  || j                                y r@   )	r$   get_pending_separatorr5   is_modifier	is_buttonr*   on_before_presson_onboard_typingget_mod_maskr;   keys     r<   on_before_key_pressz#WordSuggestions.on_before_key_press   sa    335 	( ,,S1++C1B1B1DEr>   c                 :    | j                   j                  |       y r@   )r*   on_before_releaser   s     r<   on_before_key_releasez%WordSuggestions.on_before_key_release  s    **3/r>   c                 0   | j                   j                  |       | j                  j                         }| j                  |k7  r| j                          |j                         s| j                  d       |j                         s| j                          y y NF)
r*   on_after_releaser$   r   r5   r   is_correction_keyexpand_correctionsr   _goto_first_prediction)r;   r   separator_afters      r<   on_after_key_releasez$WordSuggestions.on_after_key_release
  sz    ))#. ++AAC++>&&($$&##E*}}'') r>   c                    |j                   }|t        j                  k(  r| j                  ||j                         n3|t        j
                  k(  r | j                  ||j                  |dk7         |t        j
                  t        j                  t        j                  fv r+| j                  j                  || j                                y y )N   )typer	   CORRECTION_TYPE_insert_correction_choicecode	WORD_TYPE_insert_prediction_choice
MACRO_TYPEr$   r   r   )r;   r   button
event_typekey_types        r<   send_key_upzWordSuggestions.send_key_up  s    88y000**39,,,**3&A+F	++!11!,,. . //T5F5F5HI.r>   c                 P    |dk(  r!| j                          | j                          y y )N   )_insert_pending_separatorr   r;   r   s     r<   on_outside_clickz WordSuggestions.on_outside_click'  s&    Q;**,&&( r>   c                 z    | j                   j                  d        | j                          | j                          y r@   )r$   set_pending_separatorr   ra   r:   s    r<   on_cancel_outside_clickz'WordSuggestions.on_cancel_outside_click,  s.    //5""$ r>   c                 R    | j                   r| j                   j                          yy)z4
        User interacted with the keyboard.
        N)r0   postpone_autosaver:   s    r<   r   z$WordSuggestions.on_activity_detected1  s      >>NN,,. r>   c                 D    | j                          | j                          y r@   )rX   ra   r:   s    r<   on_spell_checker_changedz(WordSuggestions.on_spell_checker_changed8  s    ""$ r>   c                 d    | j                          | j                          | j                          y)z, On delayed_word_separators_enabled changed N)rY   r   ra   r:   s    r<   on_punctuator_changedz%WordSuggestions.on_punctuator_changed<  s&    !""$ r>   c                     | j                   j                         }|r7| j                  j                  |       | j                   j	                  d       yy)z
        Insert pending separator at the position of the separator.
        This is only useful when text was inserted from outside of
        Onboard, e.g. by middle clicking to insert something from
        clipboard.
        N)r$   r   r*   insert_separator_at_distancer   r;   separator_spans     r<   r   z)WordSuggestions._insert_pending_separatorB  sH     **@@B99.I33D9 r>   c                 "   t         j                         rt         j                  j                  nd }| j                  j                  |       |2| j                         }|r|gng }| j                  j                  |       | j                          y r@   )	rK   is_spell_checker_enabledr   spell_check_backendr(   set_backendrf   set_dict_idsr   )r;   backendro   dict_idss       r<   rX   z%WordSuggestions._update_spell_checkerN  s|     ..0 **>>6: 	''0&&(G$+yH,,X6""$r>   c                 $    | j                          y r@   )r   r:   s    r<   invalidate_for_resizez%WordSuggestions.invalidate_for_resize\  s    ##%r>   c                     | j                          | j                          | j                          | j                         }|j	                  | j                                |S r@   )_update_correction_choices_update_prediction_choices_update_pending_separator_popupupdate_inputlineextendupdate_wordlists)r;   keys_to_redraws     r<   update_suggestions_uiz%WordSuggestions.update_suggestions_ui_  sQ    '')''),,...0d3356r>   c                 (   g }| j                         }|D ]9  }|j                  | j                  | j                        }|j	                  |       ; | j
                  r| j
                  j                          |D ]  }|j                  d        ||z   S Nr   )rQ   create_keysr1   r3   r   rT   invalidate_cachesconfigure_label)r;   r   itemsr[   keysr   s         r<   r   z WordSuggestions.update_wordlistsg  s    '') 	(D##D$<$<$($<$<>D!!$'	( ;;KK))+! 	#C"	# ~%%r>   c                     g S r@    r:   s    r<   update_language_uiz"WordSuggestions.update_language_uiy  s    	r>   c                 z    t         j                  j                  rt        |       | _        y t        |       | _        y r@   )rK   wpdelayed_word_separators_enabledPunctuatorDelayedSeparatorsr*   r)   r:   s    r<   rY   z"WordSuggestions._update_punctuator|  s)    9944:4@D<TBDr>   c                 H    t        | j                  xs | j                        S r@   )boolr1   r3   r:   s    r<   _has_suggestionsz WordSuggestions._has_suggestions  s    D,,H0H0HIIr>   c                     | j                  t              D ]6  }|j                         s|j                  |       | j	                  |g       8 y r@   )rG   rH   are_corrections_expandedr   redraw)r;   expandr[   s      r<   r   z"WordSuggestions.expand_corrections  sD    00-A 	$D,,.''/TF#	$r>   c                 X    | j                  t              D ]  }|j                           y r@   )rG   rH   goto_first_prediction)r;   r[   s     r<   r   z&WordSuggestions._goto_first_prediction  s(    00-A 	)D&&(	)r>   c                     |r?||j                         z   }|dk\  r'|t        | j                        k  r| j                  |   S y)z
        wordlist:     WordListPanel that choice_index belongs to
        choice_index: index of a visible prediction key
        r    )get_first_prediction_indexlenr3   )r;   rO   choice_indexindexs       r<   get_prediction_choicez%WordSuggestions.get_prediction_choice  sG    
  8#F#F#HHEzec$*B*B&CC//66r>   c                     | j                  ||      }| j                  j                         }| j                  j	                  |      \  }}|d d }||fS N)r  r$   get_bot_contextr0   tokenize_context)r;   rO   r   wordcontexttokensspanshistorys           r<   !get_prediction_choice_and_historyz1WordSuggestions.get_prediction_choice_and_history  sU    ))(LA##33577@"+W}r>   c                 T    | j                   r| j                   j                  |       y y r@   )r0   remove_context)r;   r  s     r<   remove_prediction_contextz)WordSuggestions.remove_prediction_context  s     >>NN))'2 r>   c                 L   | j                   }| j                         5  | j                          | j                  |j	                         |j                         | j                  j                         j	                         | j                  |          ddd       y# 1 sw Y   yxY w)z spelling correction clicked N)	r2   suppress_modifiers_delete_selected_textreplace_textbeginendr$   get_span_at_caretr1   )r;   r   r   spans       r<   r   z)WordSuggestions._insert_correction_choice  s    $$$$& 	F&&(djjlDHHJ"//AACIIK"66|DF	F 	F 	Fs   A4BB#c                    | j                  |j                         |      }| j                  |      \  }}d}d}|r\| j                  j	                         }	| j                  |	||      }| j                         }
|
j                  |j                               }| j                         5  t        j                  j                  r| j                  ||       n&| j                  |||       |r| j                  d       ddd       |r3t        |j!                         t#        |      ||j!                               nd}| j$                  j'                  |       y# 1 sw Y   \xY w)z prediction choice clicked r   NT)r  
get_parent_get_prediction_choice_changesr$   r  _simulate_insertionget_text_domainget_auto_separatorget_textr  rK   r   r   _replace_text_at_caretset_last_typed_was_separatorr   r  r   r*   set_separator)r;   r   r   allow_separatorchoicedeletion	insertion	separatorsimulated_result
caret_spandomainr   s               r<   r   z)WordSuggestions._insert_prediction_choice  sQ   ++CNN,<lK//7 	) 	 **<<>J((XyI  ))+F))*:*C*C*EF  $$& 	<yy88++Hi@ ++HiK55d;	<  ""2"8"8":"%i.)"2"8"8":< # 	
 	&&~6	< 	<s   'AEEc                    d}| j                   j                         }|r_|j                         }| j                   j                         }d}|j	                  |      r |j                         r|j                         rd}|rZ| j                          | j                  |j                         t        |      z
  |j                         |j                         |       |rw| j                         j                  |       |rU|j                         t        |      z
  t        |      z   t        |      z   }| j                  ||t        |      z   |d       yyy)zT
        Insert a word/word-remainder and add a separator string as needed.
        FTr   N)r$   get_selection_spanget_text_after_spanget_line_past_caret
startswithisspacer  r  r  r   get_text_changerinsert_string_at_caret)	r;   r$  r%  auto_separatordelete_existing_separatorselection_spanremaining_textremaining_lineposs	            r<   r  z&WordSuggestions._replace_text_at_caret  s@    %*! **==?+??AN!..BBDN )-%!,,^<%%'N,B,B,D,1)&&(n224s8}D,224,224') !!#::>J($**,s8}<	N#%(%89!!#"%N(;";"%"$& ) r>   c                 2   t         j                  ry d}d }| j                  j                         }|r|j	                         dz
  }|dk\  r| j                  j                  |      }|rp|xj                  |j                  z  c_        t        t        |j                  |j                  dz        |j                  dz        |_        |j                         sd}|r/| j                         }|r| j                  j                  ||       y| j                          y)NFre   r   r   r   T)rK   xid_moder$   r   r  get_character_extentsxwmaxminhis_emptyget_main_viewr+   show_athide_pending_separator_popup)r;   showrectr  offsetviews         r<   r   z/WordSuggestions._update_pending_separator_popup  s    ??  668ZZ\A%F{((>>vFFFdff$F !TVVTVVaZ!8$&&1*EDF==?#%%'D--55dDA  --/r>   c                 n    | j                   j                         r| j                   j                          y y r@   )r+   
is_visiblehider:   s    r<   rC  z,WordSuggestions.hide_pending_separator_popup  s+    ((335))..0 6r>   c                     |j                         }|r|dt        |        }||z  }|j                         }t        |t        |      z   d||      S )a  
        Return the context up to caret_span with deletion
        and insertion applied.

        Doctests:
        >>> ws = WordSuggestions()
        >>> def test(caret_span, deletion, insertion):
        ...     span = ws._simulate_insertion(caret_span, deletion, insertion)
        ...     return span.begin(), span.text, span.text_begin()
        >>> test(TextSpan(0, 0, ""), "", "")
        (0, '', 0)
        >>> test(TextSpan(0, 0, ""), "", "local")
        (5, 'local', 0)
        >>> test(TextSpan(0, 0, ""), "log", "")
        (0, '', 0)
        >>> test(TextSpan(8, 0, "", 5), "log", "")
        (5, '', 5)
        >>> test(TextSpan(3, 0, ""), "log", "local")
        (5, 'local', 0)
        >>> test(TextSpan(6, 0, "/var/l"), "", "og")
        (8, '/var/log', 0)
        >>> test(TextSpan(8, 0, "/var/log"), "log", "local")
        (10, '/var/local', 0)
        >>> test(TextSpan(8, 0, "/var/log"), "log", "")
        (5, '/var/', 0)
        Nr   )get_text_until_spanr   
text_beginr   )r;   r(  r$  r%  r  text_poss         r<   r  z#WordSuggestions._simulate_insertion  sY    6 002oH~.G9((*3w</GXFFr>   c                 $   g | _         d | _        | j                  rvt        j	                         ra| j
                  j                         }|rD| j                  || j                               }|r!| j                  |d      \  | _         | _        }y y y y y r   )
r1   r2   r(   rK    are_spelling_suggestions_enabledr$   r  _get_word_to_spell_check	is_typing_find_correction_choices)r;   r(  	word_spanauto_capitalizations       r<   r   z*WordSuggestions._update_correction_choices?  s    #%  $224**<<>J 99*:>..:JL	 55iG*T-*(   5 r>   c                    g }d}d}|j                         }|j                         }| j                  j                         }||z
  }	| j                  j                  ||	      \  }
}|r|}t        |
d   |z   |
d   |
d   z
  |
d   |
d   |z         }|rs|d   }|j                         |j                         k(  r|}nJ|rH|d   j                         r5|j                         }| j                  j                  ||	      \  }
}|s|}|}|||fS )au  
        Find spelling suggestions for the word at or before the caret.

        Doctests:
        >>> ws = WordSuggestions()
        >>> f = ws._find_correction_choices
        >>> ws._spell_checker.set_backend(0)
        >>> ws._spell_checker.set_dict_ids(["en_US"])
        True

        # auto-capitalization
        >>> f(TextSpan(0, 7, "Jupiter"), True)
        ([], None, None)
        >>> f(TextSpan(0, 7, "jupiter"), True)             # doctest: +ELLIPSIS
        (['Jupiter', ...], TextSpan(0, 7, 'jupiter', 0, None), 'Jupiter')
        >>> f(TextSpan(0, 7, "jupiter-orbit"), True)       # doctest: +ELLIPSIS
        (['Jupiter', ...], TextSpan(0, 7, 'jupiter', 0, None), 'Jupiter')
        >>> f(TextSpan(0, 3, "usa"), True)                 # doctest: +ELLIPSIS
        (['USA', ...], TextSpan(0, 3, 'usa', 0, None), 'USA')
        >>> f(TextSpan(0, 3, "Usa"), True)                 # doctest: +ELLIPSIS
        (['USA', ...], TextSpan(0, 3, 'Usa', 0, None), 'USA')
        >>> f(TextSpan(0, 13, "ubuntu-system"), True)      # doctest: +ELLIPSIS
        ([...], TextSpan(0, 13, 'ubuntu-system', 0, None), 'Ubuntu-system')
        Nr   re   r   )
rM  get_span_textr$   	get_caretr(   find_correctionsr   upperislower
capitalize)r;   rT  auto_capitalizecorrection_choicescorrection_spanrU  rM  r  caretrF  r  choicesr#  	word_capss                 r<   rS  z(WordSuggestions._find_correction_choicesO  s1   2  "))+
&&(!!++-# 00v> 	g!(&tAw';'+Awa'8'+Aw'+Aw';=O +A.::<6<<>1*0'd1goo/ $ 1I++<<YO "D'".7+*3!?4GGGr>   c                    g | _         | j                  }| j                  rV|j                         }|r9|j	                         }|j                         }| j                  j                  |      \  }}| j                  |t        | j                  d         |      \  }}}	}
| j                  j                  |t        j                  j                  dz  |dk(  |dk(  t        j                  j                  |      }g }|D ]X  }|j                  d      r|
r1|j!                         }||k7  r| j                  j#                  |      rH|j%                  |       Z |	r| j'                  |      }ng }|| _         yy)z8 word prediction: find choices, only once per key press re      r   )case_insensitivecase_insensitive_smartaccent_insensitive_smartignore_non_capitalizedz<bot:N)r3   r$   r0   get_contextget_text_begin_markerget_pending_bot_contextr  _get_prediction_optionsr   modspredictrK   r   max_word_choicesaccent_insensitiver.  lowerword_existsappend_capitalize_choices)r;   r$   r  
bot_markerbot_contextr	  r
  case_insensitive_modeignore_non_capsr\  drop_capitalized_choicesra  r#  choice_lowers                  r<   r   z*WordSuggestions._update_prediction_choices  sp   #% ((>>"..0G)??A
*BBD $ ? ? L 0015diil1C1;=/&-
  >>11II..2%:a%?+@A+E-3YY-I-I+: 2 < & +F ((1  ('-||~!\1>>55lC$NN6*+" "66w?G'.D$] r>   Nc                 D   | r| d   }t        |       dk\  xr | d   dk(  xs t        |      xr | d   |k(  }t        |      xr |d   j                         }t        |       }| xr
 |xs |xr |}|xr |xs |}|xs |xs | }	|	sd}
n|rd}
n	d}
nd}
d}d}d}|
|||fS )ak  
        Determine prediction options.

        Doctests:
        >>> get_options = WordSuggestions._get_prediction_options

        # mid-sentence, no tokens
        >>> get_options([], False)
        (0, False, False, False)

        # mid-sentence, lowercase
        >>> get_options(["<s>", "Word", "prefix"], False)
        (2, False, False, False)

        # mid-sentence, capitalized
        >>> get_options(["<s>", "Word", "Prefix"], False)
        (0, True, False, False)

        # mid-sentence, capitals in word (Irish, e.g. bPoblacht)
        # Don't drop caps words, smart case-insensitive search already dropped
        # the lower caps variant.
        >>> get_options(["<s>", "Word", "pRefix"], False)
        (2, False, False, False)

        # mid-sentence, empty prefix
        >>> get_options(["<s>", "Word", ""], False)
        (2, False, False, False)

        # mid-sentence, empty prefix, with shift
        >>> get_options(["<s>", "Word", ""], True)
        (0, True, False, False)

        # sentence begin, lowercase
        >>> get_options(["<s>", "prefix"], False)
        (2, False, False, False)

        # sentence begin, capitalized
        >>> get_options(["<s>", "Prefix"], False)
        (1, False, True, False)

        # sentence begin, empty prefix
        >>> get_options(["<s>", ""], False)
        (2, False, False, False)

        # sentence begin, empty prefix, with shift
        >>> get_options(["<s>", ""], True)
        (1, False, True, False)

        # sentence begin at begin of text.
        >>> get_options(["<bot:txt>", "Prefix"], True, "<bot:txt>")
        (1, False, True, False)
        r  r   <s>r   re   F)r   r   isupper)r	  shiftru  prefixsentence_begincapitalizedempty_prefixrx  r\  re  rw  ry  s               r<   rl  z'WordSuggestions._get_prediction_options  s    l BZF!&kQ. N%bzU2  M#J/LF2J*4L  v,>6!9+<+<+>K#F|+L$2 2 !H!,!F0F   .H;3G%J-Kk6JU1K#()%,-),-) %&!$OJ %,. 	.r>   c                    g }|j                         r| j                  j                  |      \  }}t        |      D ]  \  }}|\  }}}	||| }
t	        |||
      }t        d ||   D              |_        t        d ||   D              |_        |
|	k7  |_        | j                  r | j                  j                  |
      |_        |j                  |        |S )Nc              3   &   K   | ]	  }|d k(    yw)re   Nr   .0counts     r<   	<genexpr>z1WordSuggestions.get_word_infos.<locals>.<genexpr>"       &Ieuz&I   c              3   &   K   | ]	  }|d k    ywr   Nr   r  s     r<   r  z1WordSuggestions.get_word_infos.<locals>.<genexpr>#  r  r  )rstripr0   lookup_text	enumerateWordInfoanyexact_matchpartial_matchignoredr(   find_incorrect_spansspelling_errorsrs  )r;   textwistokspanscountsitstartr  tokenr  wis               r<   get_word_infoszWordSuggestions.get_word_infos  s    ;;=#~~99$?Hf!(+ 
1$%!sEE#eS$/#&&Ivay&I#I#&&Ivay&I#I #'5=
&&++@@F &

2
 
r>   c                     g }t               }| D ]E  }|s|dd j                         |dd z   }||vs$|j                  |       |j                  |       G |S )a  
        Set first letters to upper case and remove
        double entries created that way.

        Doctests:
        >>> WordSuggestions._capitalize_choices(
        ...     ["word1", "Word1", "Word2", "word3"])
        ['Word1', 'Word2', 'Word3']
        >>> WordSuggestions._capitalize_choices(
        ...     ["Word", "woRD", "WORD"])
        ['Word', 'WoRD', 'WORD']
        Nre   )r   rZ  rs  add)ra  resultsseenr#  s       r<   rt  z#WordSuggestions._capitalize_choices,  sh     u 	%F))+fQRj8%NN6*HHV$	% r>   c                     d}d}| j                   r`| j                  j                         }| j                   j                  |      }|t	        |      d }||z   |k(  r|}|s	|s|d   }|}||fS |}|}||fS )zu
        Determines the text changes necessary when inserting
        the prediction choice at choice_index.
        r   Nr  )r0   r$   ri  get_last_context_fragmentr   )r;   r#  r$  r%  r  word_prefix	remainders          r<   r  z.WordSuggestions._get_prediction_choice_changesE  s    
 	>>''335G..BB7KKs;/01I Y&&0%	
   %bzH (I
 "" '"	""r>   c                     | j                  |      }|rM| j                  |      r<| j                  |      }|j                         }|r|r|j	                         |k(  rd}|S y)a-  
        Get the word to be spell-checked.

        Doctests:
        >>> from Onboard.TextDomain import DomainGenericText
        >>> wp = WordSuggestions()
        >>> wp._wpengine = WPLocalEngine()
        >>> d = DomainGenericText()
        >>> wp.get_text_domain = lambda : d

        # not typing and caret at word begin: return word at caret
        >>> span = TextSpan(9, 0, "binomial proportion")
        >>> print(wp._get_word_to_spell_check(span, False))
        TextSpan(9, 10, 'proportion', 9, None)

        # not typing and caret before word end: return word at caret
        >>> span = TextSpan(7, 0, "binomial proportion")
        >>> print(wp._get_word_to_spell_check(span, False))
        TextSpan(0, 8, 'binomial', 0, None)

        # not typing and caret at word end: return word before caret
        >>> span = TextSpan(8, 0, "binomial proportion")
        >>> print(wp._get_word_to_spell_check(span, False))
        TextSpan(0, 8, 'binomial', 0, None)

        # not typing and caret in URLs or filenames: no spelling suggestions
        >>> span = TextSpan(57, 0,
        ...  "abc http://user:pass@www.do-mai_n.nl/path/name.ext/?p=1#anchor ")
        >>> print(wp._get_word_to_spell_check(span, False))
        None
        >>> span = TextSpan(2, 0, "http://www.domain.org")
        >>> print(wp._get_word_to_spell_check(span, False))
        None

        # typing and caret at word end: suppress spelling suggestions
        >>> span = TextSpan(8, 0, "binomial proportion")
        >>> print(wp._get_word_to_spell_check(span, True))
        None
        N)_get_section_before_span_can_spell_check_get_word_before_spanr  r  )r;   r(  rR  section_spanrT  r`  s         r<   rQ  z(WordSuggestions._get_word_to_spell_checkb  sh    P 44Z@  .22:>I $$&E}}%' 	r>   c                     | j                   j                         r\| j                   j                         }|j                         s1| j                   j	                  |j
                  |j                         yyy)z/
        Delete the current selection.
        N)r$   can_insert_textr+  r@  delete_textr7  length)r;   r4  s     r<   r  z%WordSuggestions._delete_selected_text  sf     ,,.!..AACN!**,!!--n.@.@.<.C.CE -
 r>   c                     | j                   j                         r| j                  ||||       y| j                  ||||       yzE
        Replace text from <begin> to <end> with <new_text>,
        N)r$   r  _replace_text_direct_replace_text_key_strokesr;   r  r  r`  new_texts        r<   r  zWordSuggestions.replace_text  s>     ,,.%%eS%B**5#uhGr>   c                     ||kD  r| j                   j                  |||z
         | j                   j                  ||       yr  )r$   r  insert_textr  s        r<   r  z$WordSuggestions._replace_text_direct  s;     ;))%u=%%eX6r>   c                    | j                         5  ||z
  }||z
  }|dk\  r9| j                  j                  d|       | j                  j                  d|       nM| j                  j                  dt        |             | j                  j                  d|t        |      z
         | j                  j	                  |       |dk\  r| j                  j                  d|       ddd       y# 1 sw Y   yxY w)z
        Replace text from <begin> to <end> with <new_text>,
        Fall-back for text entries without support for direct text insertion.
        r   left	backspacedeleterightN)r  text_changer_key_strokepress_keysymsabspress_key_string)r;   r  r  r`  r  r  rF  s          r<   r  z)WordSuggestions._replace_text_key_strokes  s     $$& 	L5[FS[F {,,::66J,,::;O,,::c&k+,,::#f+!57 ((99(C {,,::7FK'	L 	L 	Ls   CC,,C5c                    | j                          t        j                  j                         dk(  rt        j                  j	                  d       | j
                  j                  d       | j                  j                          y)z$ The current accessible lost focus. re   r   N)	rA   rK   word_suggestionsget_pause_learningset_pause_learningr$   r   text_changer_direct_insertstop_auto_repeatr:   s    r<   on_text_entry_deactivatedz)WordSuggestions.on_text_entry_deactivated  sa     ""5571<##66q9//5''88:r>   c                 8    | j                   j                          y)z, A different target widget has been focused N)r&   on_text_entry_activatedr:   s    r<   r  z'WordSuggestions.on_text_entry_activated  s    446r>   c                 z    t         j                         r'| j                  |      }|r| j                  ||       yyy)z) Synchronous callback for text insertion N)rK   is_auto_capitalization_enabled_get_word_to_auto_correct_auto_correct_at)r;   insertion_spancaret_offsetrT  s       r<   on_text_insertedz WordSuggestions.on_text_inserted  s=     00266~FI%%i>  3r>   c                     |r\| j                  d       | j                          | j                          | j                          | j                  j                          yy)z
        Asynchronous callback for generic context changes.
        The text of the target widget changed or the caret moved.
        Use this for low priority display stuff.
        FN)r   r   r   ra   r&   on_text_context_changed)r;   change_detecteds     r<   r  z'WordSuggestions.on_text_context_changed  sP     ##E*'')&&(""$  88: r>   c                      y)z Caret position changed. Nr   r:   s    r<   on_text_caret_movedz#WordSuggestions.on_text_caret_moved  s    r>   c                 R    | j                   xr | j                   j                         S )z& Are there any text changes to learn? )r$   has_changesr:   s    r<   r  zWordSuggestions.has_changes  s'    !! 0!!--/	1r>   c                 x    | j                         r| j                  j                          | j                          y)z. Learn all accumulated changes and clear them N)r  r&   rA   _clear_changesr:   s    r<   rA   zWordSuggestions.commit_changes  s,      //1r>   c                     t         j                  d       | j                  j                          | j	                          y)zN
        Discard all changes that have accumulated, don't learn them.
        zdiscarding changesN)rh   ri   r&   discard_changesr  r:   s    r<   r  zWordSuggestions.discard_changes  s/     	)*,,.r>   c                    | j                   j                          | j                   j                          | j                  j                          | j                  r| j                  j                          | j                  d       y)z;
        Reset all contexts and clear all changes.
        FN)r#   clear_changesrB   r"   r(   invalidate_query_cacher   r:   s    r<   r  zWordSuggestions._clear_changes  sc     	--/%%' 668))%0r>   c                 L   g }| j                   }|r| j                  r| j                         D ]  }t        j                  j
                  r| j                  r1|j                  s6|j                  |d       |j                  |       Z| j                  j                         }|r#|j                          |j                  |d       nd}|j                  |d       |j                  || j                  | j                  j                                |j                  |        |S )z7 Refresh the GUI displaying the current line's content FTr   )rT   r0   rS   rK   r  show_context_liner6   rR   set_item_visiblers  r$   get_lineraise_to_topset_contentr4   get_line_caret_pos)r;   r   rT   r   lines        r<   r   z WordSuggestions.update_inputline  s    dnn--/ /..@@(({{//U;&--c2,,557D((*//T:!//U;OOD$//$($5$5$H$H$JL"))#.#/& r>   c                 p    | j                   |k7  r'|| _         | j                  | j                                yy)zJ
        Temporarily hide the input line to access keys below it.
        N)r6   r   r   )r;   rJ  s     r<   hide_input_linezWordSuggestions.hide_input_line9  s4       D($(D!KK--/0 )r>   c                 P    | j                   r| j                  |      sd| _         y y y r   )r6   _key_intersects_input_liner   s     r<   show_input_line_on_key_releasez.WordSuggestions.show_input_line_on_key_releaseA  s+      ..s3$)D! 4 !r>   c                     | j                         D ]1  }|j                         j                  |j                               s1 y y)z3 Check if key rect is intersecting the input line. TF)rS   get_border_rect
intersects)r;   r   r[   s      r<   r  z*WordSuggestions._key_intersects_input_lineF  sB    **, 	D##%001D1D1FG	 r>   c                 6    | j                   j                         S r@   )r$   r  r:   s    r<   r  zWordSuggestions.get_text_domainM  s      0022r>   c                 J    | j                         }|r|j                         syy)z? Password entries may block feedback to prevent eavesdropping. FT)r  can_give_keypress_feedback)r;   r)  s     r<   r  z*WordSuggestions.can_give_keypress_feedbackP  s$    %%'&;;=r>   c                 L    | j                         }|r|j                  |      ryy)z- No spell checking for passwords, URLs, etc. TF)r  can_spell_check)r;   span_to_checkr)  s      r<   r  z WordSuggestions._can_spell_checkW  s&    %%'f,,];r>   c                 L    | j                         }|r|j                  |      ryy)z+ No auto correct for passwords, URLs, etc. TF)r  can_auto_correct)r;   r  r)  s      r<   _can_auto_correctz!WordSuggestions._can_auto_correct^  s&    %%'f--l;r>   c                 6    | j                   j                         S )z; No auto-punctuation in terminal when prompt was detected. )r$   can_auto_punctuater:   s    r<   _can_auto_punctuatez#WordSuggestions._can_auto_punctuatee  s      3355r>   c                     | j                   dk(  rJt               j                          | j                         }|r|j	                          | j                          | xj                   dz  c_         y)z
        Turn off AT-SPI listeners while there is a dialog open.
        Onboard and occasionally the whole desktop tend to lock up otherwise.
        Call this before dialog/popop menus are opened by Onboard itself.
        r   re   N)r9   r   freezeget_applicationon_focusable_gui_openingstop_raise_attemptsr;   apps     r<   r  z(WordSuggestions.on_focusable_gui_openingi  s]       A%&&( &&(C,,.$$&"r>   c                     | xj                   dz  c_         | j                   dk(  r<t               j                          | j                         }|r|j	                          yyy)zA
        Call this after dialogs/menus have been closed.
        re   r   N)r9   r   thawr  on_focusable_gui_closedr  s     r<   r  z'WordSuggestions.on_focusable_gui_closed{  s[     	"  A%$$& &&(C++-  &r>   c                      | j                   dkD  S r   )r9   r:   s    r<   has_focusable_guiz!WordSuggestions.has_focusable_gui  s    $$q((r>   c                    | j                  |dt        j                  j                        \  }}|rJ| j	                         5  | j                  |j                         |j                         ||       ddd       yy# 1 sw Y   yxY w)z6
        Auto-capitalize/correct a word_span.
        TN)_find_auto_correctionrK   r   auto_correctionr  r  r  r  )r;   rT  r  r_  replacements        r<   r  z WordSuggestions._auto_correct_at  s    
 &&4!9!9!I!IK 	% ((* /!!/"7"7"9"1"5"5"7"."-// / / /s   1A<<Bc                     | j                  ||      \  }}}|}|sD|rB|r@d}d}	|j                         }
t        |
      |kD  r|d   }| j                  |
|      }||	k  r|}||fS )ac  
        Doctests:
        >>> wp = WordSuggestions()
        >>> wp._spell_checker.set_backend(0)
        >>> wp._spell_checker.set_dict_ids(["en_US"])
        True

        # auto-capitalization
        >>> wp._find_auto_correction(
        ...     TextSpan(0, 7, "jupiter ..."), True, False)
        (TextSpan(0, 7, 'jupiter', 0, None), 'Jupiter')
        >>> wp._find_auto_correction(TextSpan(0, 3, "usa ..."), True, False)
        (TextSpan(0, 3, 'usa', 0, None), 'USA')
        >>> wp._find_auto_correction(TextSpan(2, 3, "  Usa ..."), True, False)
        (TextSpan(0, 3, 'Usa', 0, None), 'USA')
        r   r   )rS  rW  r   _string_distance)r;   rT  r]  auto_correctr^  r_  rU  r  min_auto_correct_lengthmax_string_distancer  r#  distances                r<   r  z%WordSuggestions._find_auto_correction  s    * )))_E				 *&'#"#**,D4y22+A.00v>22"(K++r>   c                     |j                         }|j                         r7| j                  |      }|r$| j                  |      r| j	                  |      }|S y)aG  
        Doctests:
        >>> from Onboard.TextDomain import DomainGenericText
        >>> wp = WordSuggestions()
        >>> wp._wpengine = WPLocalEngine()
        >>> d = DomainGenericText()
        >>> wp.get_text_domain = lambda : d

        >>> print(wp._get_word_to_auto_correct(TextSpan(6, 1, "abc def")))
        None

        >>> print(wp._get_word_to_auto_correct(TextSpan(7, 1, "abc def ")))
        TextSpan(4, 3, 'def', 4, None)

        >>> print(wp._get_word_to_auto_correct(TextSpan(7, 1, "abc def.")))
        None

        >>> print(wp._get_word_to_auto_correct(TextSpan(8, 1, "abc def. ")))
        TextSpan(4, 3, 'def', 4, None)

        >>> print(wp._get_word_to_auto_correct(TextSpan(56, 6,
        ...     "abc http://user:pass@www.do-mai_n.nl/path/name.ext/"
        ...     "?p=1#anchor ")))
        None
        N)get_last_char_in_spanr/  r  r  r  )r;   r  charr  rT  s        r<   r  z)WordSuggestions._get_word_to_auto_correct  sU    4 335<<>88HL%%l3 66~F	  r>   z\S*\s*$z
\S*(?=\s*)c                    |j                         }| j                  j                  |      }|r|j                         }|j                  |d }| j
                  j                  |      }|r]|j                         }|j                         |j                         z
  |_        |j                  |z   |j                         z   |_
        |S y)a&  
        Get first whitespace-separated section that
        begins before the given span.

        Doctests:
        >>> wp = WordSuggestions()

        # If input span intersects a word anywhere, return the whole word
        >>> wp._get_section_before_span(TextSpan(6, 1, "abc def"))
        TextSpan(4, 3, 'def', 0, None)

        # If input span lies just past a word, return the whole word
        >>> wp._get_section_before_span(TextSpan(7, 1, "abc def "))
        TextSpan(4, 3, 'def', 0, None)

        # If input span intersects a URL anywhere, return the whole URL
        >>> wp._get_section_before_span(TextSpan(56, 6,
        ...     "abc http://user:pass@www.do-mai_n.nl/path/name.ext/"
        ...     "?p=1#anchor"))
        TextSpan(4, 58, 'http://user:pass@www.do-mai_n.nl/path/name.ext/?p=1#anchor', 0, None)

        # If input span intersects a URL anywhere, return the whole URL
        >>> wp._get_section_before_span(TextSpan(6, 2,
        ...     "abc http://www.domain.org"))
        TextSpan(4, 21, 'http://www.domain.org', 0, None)

        # Still return section before span if text_pos != 0
        >>> wp._get_section_before_span(TextSpan(10+32, 1,
        ...     "http://www.domain.org/index.html ", 10))
        TextSpan(10, 32, 'http://www.domain.org/index.html', 10, None)
        N)rL  _section_begin_patternsearchr  r  _section_end_patterncopyr  r  rN  r7  )r;   r  r  matchr  r  s         r<   r  z(WordSuggestions._get_section_before_span  s    B 113++2248KKME!&&uv.D--44T:E%**,#iikEKKM9==505;;=@r>   c                 |   d}|r| j                   r| j                   j                  |j                               \  }}|j                         }|j	                         |z
  }d}t        |      D ]  \  }}	|	d   |kD  r n|} |9t        ||         }
|
dvr'||   d   |z   }||   d   |z   }t        |||z
  |
|      }|S )a  
        Get the word at or before the span.

        Doctests:
        >>> wp = WordSuggestions()
        >>> wp._wpengine = WPLocalEngine()

        # caret right in the middle of a word
        >>> wp._get_word_before_span(
        ...     TextSpan(15, 0, "binomial proportion"))
        TextSpan(9, 10, 'proportion', 9, None)

        # text at offset
        >>> wp._get_word_before_span(
        ...     TextSpan(25, 0, "binomial proportion", 10))
        TextSpan(19, 10, 'proportion', 19, None)

        # caret after whitespace - get the previous word
        >>> wp._get_word_before_span(
        ...     TextSpan(9, 0, "binomial  proportion"))
        TextSpan(0, 8, 'binomial', 0, None)
        Nr   )z<unk>z<num>r~  re   )r0   tokenize_textr  rM  r  r  r   r   )r;   r  rT  r	  r
  rM  local_caretitokenr  sr  bes                r<   r  z%WordSuggestions._get_word_before_span  s    . 	DNN NN88IMFE*J**,3KF!%( 1Q4+%
 !#F6N3  99fa(:5Afa(:5A (AE5! <Ir>   c           	      P   t        |       }t        |      }d}t        t        d|dz               dgz   }t        |      D ]]  }|}dg|z  |dz   gz   }t        |      D ]>  }t        | |   ||   k7        }t	        ||dz
     dz   ||   dz   ||dz
     |z         ||<   @ _ ||dz
     S )z Levenshtein string distance Nre   r   )r   r   rangeintr>  )	s1s2len1len2last_rowrowricids	            r<   r  z WordSuggestions._string_distanceE  s     2w2w5D1H%&!,+ 	4BH#*Qx'CDk 42"R&()c"q&kAo&rlQ.&rAv.24B4	4 4!8}r>   )r   r@   T)`__name__
__module____qualname____doc__r=   rB   rD   rM   rJ   rW   rc   r^   rn   r~   r   rf   r   r   r   rQ   rS   r   r   r   r   r   r   r   r   r   r   r   r   r   rX   r   r   r   r   rY   r   r   r   r  r  r  r   r   r  r   rC  r  r   rS  r   staticmethodrl  r  rt  r  rQ  r  r  r  r  r  r  r  r  r  r  rA   r  r  r   r  r  r  r  r  r  r  r  r  r  r
  r  r  r  recompiler  r  r  r  r  r   r>   r<   r   r   6   s#   /"8%L
"*)&!#,JD88!)$#<,-F0*J)
!
/!!
:%&&$CJ$)	3F$7L$&L<1 GDH :Hx3/r S. S.j$  0#:6pH7L6
;7?;1
141*
36#$.)/&,P!F (RZZ
3%2::m4,\-^  r>   r   c                   X    e Zd ZdZddZ	 	 ddZddZ	 	 ddZ	 	 ddZddZ	d	 Z
d
 Zy)LearnStrategyz)
    Base class of learn strategies.
    Nc                 B    |r|| _        y t         j                  | _        y r@   )	pypredictr  	_tokenize)r;   tokenizes     r<   r=   zLearnStrategy.__init__\  s     "&44 	r>   c                     t         j                  j                         r`| j                  ||||      }| j                  j
                  }|D ]0  }|j                  |t         j                  j                                2 y y r@   )	rK   r   can_auto_learn_get_learn_texts_wpr0   
learn_textr  can_learn_new_words)r;   r
  ru  
bot_offsettext_domaintextsenginer  s           r<   _learn_spanszLearnStrategy._learn_spans`  su    99##%))%Z*57E
 XX''F I!!&11EEGII &r>   c                     t         j                  j                         rV| j                  j                  }|j                          |r-| j                  |d d |      }|D ]  }|j                  |        y y y r@   )rK   r   r@  rB  r0   clear_scratch_modelsrA  learn_scratch_text)r;   r
  rF  rH  rG  r  s         r<   _learn_scratch_spansz"LearnStrategy._learn_scratch_spansm  sm    99##%XX''F'')--eT4M! 4D--d34  &r>   c                 r    | j                  ||||      }|D cg c]  }dj                  |       c}S c c}w )N )_get_learn_tokensjoin)r;   r
  ru  rE  rF  
token_setsr	  s          r<   rA  zLearnStrategy._get_learn_textsw  s8    ++E:z,79
/9:V :::s   4c                    g }g }|r| j                  ||      }t        |d       }|D ]  }| j                  |      \  }}	}
d}|ry|rN|d   }|d   }|
r|
n|	d   }t        |      D ],  \  }}||k(  s|
r|dz   n|}|d| |z   |d<   |d| |	z   |d<   d}. n)|r'|%|
|
d   |cxk  r|	d   d   k  rn n|g|z   }d	g|	z   }	|rt	        |      s|j                  |       |j                  |	        |S )
ak  
        Get disjoint sets of tokens to learn.
        Tokens of overlapping or adjacent spans are joined.
        Expected overlap is at most one word at begin and end.

        Doctests:
        >>> from Onboard.TextDomain import DomainGenericText
        >>> p = LearnStrategy()
        >>> d = DomainGenericText()

        # single span
        >>> p._get_learn_tokens([TextSpan(14, 2, "word1 word2 word3")])
        [['word3']]

        # multiple distinct spans
        >>> p._get_learn_tokens([TextSpan( 3, 1, "word1 word2 word3"),
        ...                      TextSpan(14, 2, "word1 word2 word3")])
        [['word1'], ['word3']]

        # multiple joined spans
        >>> p._get_learn_tokens([TextSpan( 3, 4, "word1 word2 word3"),
        ...                      TextSpan(10, 1, "word1 word2 word3"),
        ...                      TextSpan(14, 2, "word1 word2 word3")])
        [['word1', 'word2', 'word3']]

        # single span with preceding sentence begin
        >>> p._get_learn_tokens([TextSpan(9, 2, "word1. word2 word3")])
        [['<s>', 'word2']]

        # Multiple joined spans across sentence marker.
        >>> p._get_learn_tokens([TextSpan(2, 2, "word1. word2 word3"),
        ...                      TextSpan(9, 2, "word1. word2 word3")])
        [['word1', '<s>', 'word2']]

        # Begin of text marker, generic text.
        >>> p._get_learn_tokens(
        ...     [TextSpan(2, 2, "word1 word2")], "<bot:txt>", 0)
        [['<bot:txt>', 'word1']]
        >>> p._get_learn_tokens(
        ...     [TextSpan(8, 2, "word1 word2")], "<bot:txt>", 0)
        [['word2']]

        # Begin of text marker, terminal.
        >>> p._get_learn_tokens([TextSpan(9, 2, "prompt$ word1 word2")],
        ...                               "<bot:term>", 8)
        [['<bot:term>', 'word1']]
        >>> p._get_learn_tokens([TextSpan(11, 2, "prompt$   word1 word2")],
        ...                               "<bot:term>", 8)
        [['<bot:term>', 'word1']]
        >>> p._get_learn_tokens([TextSpan(13, 2, "prompt$ 123 word1 word2")],
        ...                               "<bot:term>", 8)
        [['word1']]
        >>> p._get_learn_tokens([TextSpan(15, 2, "prompt$ word1 word2")],
        ...                               "<bot:term>", 8)
        [['word2']]

        # The hierarichcal part of URLs is considered changed as a whole
        >>> p._get_learn_tokens(
        ...     [TextSpan(14, 1, "http://www.domain.org/home/index.html")],
        ...                          None, None, d)
        [['http', 'www', 'domain', 'org', 'home', 'index', 'html']]

        # Recognize URLs even without the scheme part
        >>> p._get_learn_tokens([TextSpan(8, 1, "www.domain.org")],
        ...                          None, None, d)
        [['www', 'domain', 'org']]

        # The query part of an URL isn't automatically included.
        >>> p._get_learn_tokens(
        ...     [TextSpan(14, 1, "http://www.domain.org/?p=1")],
        ...                          None, None, d)
        [['http', 'www', 'domain', 'org']]

        # The fragment part of an URL isn't automatically included.
        >>> p._get_learn_tokens(
        ...     [TextSpan(14, 1, "http://www.domain.org/#anchor")],
        ...                          None, None, d)
        [['http', 'www', 'domain', 'org']]

        # File paths are considered changed as a whole
        >>> p._get_learn_tokens([TextSpan(6, 1, "/usr/bin/onboard")],
        ...                          None, None, d)
        [['usr', 'bin', 'onboard']]
        >>> p._get_learn_tokens([TextSpan(5, 1, "usr/bin/onboard")],
        ...                          None, None, d)
        [['usr', 'bin', 'onboard']]

        # Passwords in URLs must not be learned
        >>> p._get_learn_tokens([TextSpan(0, 11, "user:pass@www.domain.org")],
        ...                          None, None, d)
        [['user', '<unk>', 'www', 'domain', 'org']]
        c                 B    | j                         | j                         fS r@   )r  r  )r;  s    r<   <lambda>z1LearnStrategy._get_learn_tokens.<locals>.<lambda>  s    qwwy!%%'6J r>   )r   Fr  r   re   NT)r  r  )_grow_spanssorted_tokenize_spanr  r   rs  )r;   
text_spansru  rE  rF  rR  	span_sets	text_spanr	  r
  span_beforemergedprev_tokens
prev_spans	link_spanr  	prev_spanks                     r<   rP  zLearnStrategy._get_learn_tokens}  sO   ~ 
	))*kBJJ,JK
# !	(I *.)<)<Y)G&FE;F",R.K"+B-J/:aI(1*(= *9$	1)4A!A-8!_v-EJrN-7^e-CIbM%)F* "!-&.&q>JE%(1+E&0\F%:F%-J$6E6{!!&)  'C!	(F r>   c                 0   |j                         }| j                  |j                               \  }}| j                  |||      }|r/|r-|d   }t	        ||      }t        t        ||z
  |            |z   }n,|r*|d   }|dkD  r ||dz
     dk(  r|j                  d|dz
         d}	|r&|d   dkD  r|d   dz
  }
|||
   d   z   |||
   d   z   f}	|D cg c]  }t        ||          c}|D cg c]  }|||   d   z   |||   d   z   f c}|	fS c c}w c c}w )a  
        Expand spans to word boundaries and return as tokens.
        Include <prepend_tokens> tokens before the span.

        Doctests:
        >>> import Onboard.TextContext as tc
        >>> p = LearnStrategy()
        >>> p._tokenize_span(tc.TextSpan(0, 1, "word1 word2 word3"))
        (['word1'], [(0, 5)], None)
        >>> p._tokenize_span(tc.TextSpan(16, 1, "word1 word2 word3"))
        (['word3'], [(12, 17)], (6, 11))
        >>> p._tokenize_span(tc.TextSpan(8, 12, "word1 word2 word3"))
        (['word2', 'word3'], [(6, 11), (12, 17)], (0, 5))
        >>> p._tokenize_span(tc.TextSpan(5, 1, "word1 word2 word3"))
        ([], [], None)
        >>> p._tokenize_span(tc.TextSpan(4, 1, "word1 word2 word3"))
        (['word1'], [(0, 5)], None)
        >>> p._tokenize_span(tc.TextSpan(6, 1, "word1 word2 word3"))
        (['word2'], [(6, 11)], (0, 5))

        - text at offset
        >>> p._tokenize_span(tc.TextSpan(108, 1, "word1 word2 word3", 100))
        (['word2'], [(106, 111)], (100, 105))

        - prepend tokens
        >>> p._tokenize_span(tc.TextSpan(13, 1, "word1 word2 word3"), 1)
        (['word2', 'word3'], [(6, 11), (12, 17)], (0, 5))
        >>> p._tokenize_span(tc.TextSpan(1, 1, "word1 word2 word3"), 1)
        (['word1'], [(0, 5)], None)
        r   re   r~  N)	rM  r=  r  _intersect_spanr>  r   r&  insertr   )r;   r[  prepend_tokensrF  r	  r
  itokensfirstnr\  rb  r  s               r<   rX  zLearnStrategy._tokenize_span	  sI   > %%'y'9'9';<&&y&%@gAJENE*A5E23g=G AJEqyeai E)q%!), wqzA~
QA!E!HQK/%(1+1EFK0781F1I&8FMN%(1+%va';<N 	8Ns   D,Dc                 2   |D cg c]  }|j                          }}d}|D ]S  }|j                  |      \  }}}	||z   }
||j                         k  s|
|j                         kD  sD||_        ||_        d}U |rt        j                  |      \  }}|S c c}w )z8
        Grow spans to include e.g. whole URLs.
        FT)r  grow_learning_spanr  r  r7  r  r   consolidate_spans)r;   r
  rF  r"  	new_spansmodifiedr  r  r  _textr  _spans               r<   rV  zLearnStrategy._grow_spansD  s     (--!QVVX-	- 	 D#.#A#A$#G E65&.Ctzz|#TXXZ $	  *<<YGIu .s   Bc                     g }|j                         }|j                         |z
  }|j                         |z
  }t        |      D ](  \  }}	||	d   k  s||	d   kD  s|j	                  |       * |S )zH
        Returns indices of tokens the given text_span touches.
        re   r   )rM  r  r  r  rs  )
r;   r[  r	  r
  rg  rF  r  r  r  r"  s
             r<   rd  zLearnStrategy._intersect_spanY  sx     %%'"V+6)e$ 	"DAqqt|ad
q!	" r>   r@   )r   NN)r   )r2  r3  r4  r5  r=   rI  rM  rA  rP  rX  rV  rd  r   r>   r<   r:  r:  W  sN    5 =A!%I4 AE%); 59&*JX9v*r>   r:  c                   Z    e Zd ZdZdZdZd Zd Zd Zd Z	d Z
d	 Zd
 Zd Zd Zd Zd Zy)r%   zu
    Delay learning individual spans of changed text to reduce the
    rate of junk entering the language model.
    <   r   c                     t         j                  | fd       | _        t               | _        d| _        d| _        t        d      | _        y )Nc                 :    j                   j                  |       S r@   )r0   r  )r  r   s    r<   rU  z+LearnStrategyLRU.__init__.<locals>.<lambda>s  s    BLL,F,Ft,L r>   r   i  )	r:  r=   rB  r   _timer_insert_count_delete_countr   _rate_limiterr;   r   s    `r<   r=   zLearnStrategyLRU.__init__q  sC    tL	Ng%c]r>   c                     | j                   j                          | j                  j                          d| _        d| _        y r   )rv  stopry  rw  rx  r:   s    r<   rB   zLearnStrategyLRU.reset{  s4    !r>   c                    | j                          | j                  j                  }|j                         }|j	                         }|rv| j                  j
                  }|r^|j                         }|j                         }| j                  j                         }| j                  ||||       |j                          |j                          y)z Learn and remove all changes N)rB   rB  r$   get_changes	get_spansr0   rj  get_begin_of_text_offsetr  rI  rK  clear)r;   r$   changesr
  rH  ru  rE  r)  s           r<   rA   zLearnStrategyLRU.commit_changes  s    

xx,,**,!!#XX''F)??A
)BBD
113!!%ZH++-r>   c                 t    | j                   j                  }|r|j                          | j                          y)z  Discard and remove all changes N)rB  r0   rK  rB   )r;   rH  s     r<   r  z LearnStrategyLRU.discard_changes  s)    ##'')

r>   c                    | j                   j                  j                         }|j                         }d}|D ]   }|r|j                  |j                  k  s|}" g }t        |      D ]X  }||ust        j                         |j                  z
  | j                  k\  s7|j                  |       |j                  |       Z | j                  |       |j                         S )z
        Learn and remove spans that have reached their timeout.
        Keep the most recent span untouched,so it can be
        worked on indefinitely.
        N)rB  r$   r~  r  last_modifiedr   timeLEARN_DELAYrs  remove_spanrI  )r;   r  r
  most_recentr  expired_spanss         r<   commit_expired_changesz'LearnStrategyLRU.commit_expired_changes  s     ((''335!!#  	#D((4+=+=="	# K 	*D;&yy{T///43C3CC$$T*##D)		*
 	-(  ""r>   c                      y r@   r   r:   s    r<   r  z(LearnStrategyLRU.on_text_entry_activated      r>   c                     | j                   j                  j                         }|j                         s| j	                          | j                          y r@   )rB  r$   r~  r@  _handle_timed_learning_maybe_update_scratch_modelsr;   r  s     r<   r  z(LearnStrategyLRU.on_text_context_changed  s>    ((''335!'')))+r>   c                     | j                   j                  j                         }|j                         s| j                  j                         sy y y r@   )	rB  r$   r~  r@  rv  
is_runningr  POLLING_TIME_poll_changesr  s     r<   r  z'LearnStrategyLRU._handle_timed_learning  sC    ((''335!{{%%' ( "r>   c                 >    | j                         }t        |      dk7  S r   )r  r   )r;   remaining_spanss     r<   r  zLearnStrategyLRU._poll_changes  s     557?#q((r>   c                    | j                   j                  r| j                   j                  }|j                         }| j                  |j
                  k  }| j                  |j                  k  }|s|r`|j                         }|r"|j                         }|j                          }nd}|s'| j                  j                  | j                  |       yyyy)z
        Update short term memory if the time is right.
        This may be a time consuming task, so we try to limit the
        frequency of updates.
        FN)rB  r0   r$   r~  rw  insert_countrx  delete_countr  get_char_before_spanr/  ry  enqueue_update_scratch_memory)r;   r$   r  inserteddeletedr(  r  in_words           r<   r  z-LearnStrategyLRU._maybe_update_scratch_models  s     888800L"..0G ))G,@,@@H((7+?+??G7);;=
%::<D"&,,.0G#G&&..t/J/J/68  # r>   c                 @   | j                   j                  j                         }|j                         }| j	                  |       |j
                  | _        |j                  | _        |r5| j                   j                          | j                   j                          yy)zV
        Update short term memory with changes that haven't been learned yet.
        N)rB  r$   r~  r  rM  r  rw  r  rx  r   ra   )r;   	update_uir  r
  s       r<   r  z'LearnStrategyLRU._update_scratch_memory  s~     ((''335!!#!!%($11$11 HH**,HH&&( r>   N)r2  r3  r4  r5  r  r  r=   rB   rA   r  r  r  r  r  r  r  r  r   r>   r<   r%   r%   h  sK    
 KL+$#6,I)86)r>   r%   c                   b    e Zd ZdZg dZg dZeez   Zd Zd Zd Z	ddZ
d Zd	 Zd
 Zd Zd Zy)
Punctuatorz9
    Abstract base class for punctuation assistance.
    )
,:;)}]u   ’u   ”   »u   ›).?!c                 2    || _         | j                          y r@   )rB  rB   rz  s     r<   r=   zPunctuator.__init__  s    

r>   c                      y r@   r   r:   s    r<   rB   zPunctuator.reset  r  r>   c                     t               r@   NotImplementedErrorr   s     r<   r!  zPunctuator.set_separator      !##r>   c                     t               r@   r  )r;   r   pending_separator_spanbefores       r<   _handle_key_eventzPunctuator._handle_key_event  r  r>   c                     |rK|j                         }| j                  j                         j                  |       | j	                  d       yy)z( Insert pending separator at the caret. TN)rW  rB  r0  r1  r   )r;   r   strings      r<   insert_separatorzPunctuator.insert_separator  sC    #113F
 HH%%'>>vF--d3 r>   c                 .   |r|j                         }| j                         }|rp| j                  j                  j	                         st        |      dk  r=|j                         }| j                  j                  |||j                         |       yyyy)z
        Insert pending separator even if it is located at some
        distance away from the caret. Caret position will be
        restored afterwards.
        2   N)rW  _get_span_at_caretrB  r$   r  r   r  r  )r;   r   r  r(  separator_poss        r<   r   z'Punctuator.insert_separator_at_distance  s     #113F002J%%557Fb  . 4 4 6%%m&3&0&6&6&8&,. ! 	 r>   c                 L    | j                   j                  j                          y r@   )rB  r$   r  r:   s    r<   _update_text_contextzPunctuator._update_text_context/  s    557r>   c                 T    | j                   j                         j                          y r@   )rB  r0  delete_at_caretr:   s    r<   _delete_at_caretzPunctuator._delete_at_caret2  s    !!#335r>   c                 N    | j                   j                  }|j                         S r@   )rB  r$   r  )r;   r$   s     r<   r  zPunctuator._get_span_at_caret5  s     xx,,--//r>   Nr1  )r2  r3  r4  r5  punctuation_no_capitalizepunctuation_capitalizepunctuationr=   rB   r!  r  r  r   r  r  r  r   r>   r<   r  r    sO    !# !0(+DDK$$	4.&860r>   r  c                   4    e Zd ZdZd Zd Zd Zd Zd Zd Z	y)	r)   z
    Insert word separators immediately (on inserting prediction).
    Mainly adds and removes spaces around punctuation depending on
    the user action immediately after word completion.
    c                 0    t         j                  | |       y r@   r  r=   rz  s     r<   r=   z&PunctuatorImmediateSeparators.__init__@      D"%r>   c                 J    t         j                  |        d | _        d| _        y r   )r  rB   _added_separator_span_separator_removedr:   s    r<   rB   z#PunctuatorImmediateSeparators.resetC  s     %)""'r>   c                     || _         y r@   )r  r   s     r<   r!  z+PunctuatorImmediateSeparators.set_separatorH  s
    %3"r>   c                    d}| j                   r|j                         r| j                         }|r|j                         | j                   j	                         k(  rq|j                         }|| j                  v r| j                          d| _        n;|| j                  v r-| j                          d| _        t        j                         rd}d| _         |r1t        j                         r| j                  j                  d       yyy)?
        Chance to insert separators before key-press.
        FTN)r  is_text_changingr  r  r  	get_labelr  r  r  r  rK   r  rB  request_capitalization)r;   r   	caps_moder(  r  s        r<   r   z-PunctuatorImmediateSeparators.on_before_pressK  s     	 %%! 002J##%--1134==?Dt===--/26/!<!<<--/26/!@@B(,I)-D&002HH++D1 3 r>   c                      y)F
        Chance to request capitalization before key-release.
        Nr   r   s     r<   r   z/PunctuatorImmediateSeparators.on_before_releasek  s     	r>   c                 v    | j                   r-d| _         | j                  j                  j                  d       yy)@
        Chance to insert separators after key-release.
        FspaceN)r  rB  r  r  r   s     r<   r   z.PunctuatorImmediateSeparators.on_after_releaseq  s4    
 ""&+D# HH,,::7C	 #r>   N)
r2  r3  r4  r5  r=   rB   r!  r   r   r   r   r>   r<   r)   r)   :  s'    
&(
42@	Dr>   r)   c                   P    e Zd ZdZd Zd Zd ZddZd Zd Z	d	 Z
d
 ZddZd Zy)r   z(
    Insert word separators lazily.
    c                 0    t         j                  | |       y r@   r  rz  s     r<   r=   z$PunctuatorDelayedSeparators.__init__  r  r>   c                 <    t         j                  |        i | _        y r@   )r  rB   _key_down_labelsr:   s    r<   rB   z!PunctuatorDelayedSeparators.reset  s     "r>   c                 &    | j                  |       y r@   )r   r   s     r<   r!  z)PunctuatorDelayedSeparators.set_separator  s    "">2r>   Nc                 N    | j                   j                  j                  |       y)z3 Remember this separator span for later insertion. N)rB  r$   r   r   s     r<   r   z1PunctuatorDelayedSeparators.set_pending_separator  s    33NCr>   c                 J    | j                   j                  j                         S )z/ Return current pending separator span or None )rB  r$   r   r:   s    r<   r   z1PunctuatorDelayedSeparators.get_pending_separator  s    xx$$::<<r>   c                    d| j                   |<   | j                         }| j                  ||d      \  }}}|r| j                  |       d}|r/t        j                         r| j                  j                  d       | j                  |       y)r  NT)	r  r   r  r  rK   r  rB  r  r   r;   r   r   re  r  s        r<   r   z+PunctuatorDelayedSeparators.on_before_press  s    
 &*c"335""3= 	*	!!.1!N002HH++D1"">2r>   c                     | j                         }| j                  ||d      \  }}}|r1t        j                         r| j                  j                  d       yyy)r  FTN)r   r  rK   r  rB  r  r  s        r<   r   z-PunctuatorDelayedSeparators.on_before_release  sZ     335""3> 	*	002HH++D1 3 r>   c                     | j                         }| j                  ||d      \  }}}|r| j                  |       d}| j                  |       d| j                  |<   y)r  FN)r   r  r  r   r  r  s        r<   r   z,PunctuatorDelayedSeparators.on_after_release  sc     335""3> 	*	!!.1!N"">2%)c"r>   c                 8   | }d}|rt         j                  j                  rsd}|j                         s|j	                         rnF|j                         r!|rd}n0|r-|j                         dk7  rd}n|j                         rd }n|j                         r| j                         }|r| j                  ||      }|| j                  v }	|| j                  v }
|	xs |
}|j                         }|j                         }|rh| j                  j                         rN|rm||k\  rh||dz   k  r`|j!                         }|xj"                  dz  c_        |xj$                  dz  c_        |
r$d}n!|r||k(  rd}n|r||k\  r||dz   k  rd}nd }nd }|r|r|d|fS |d|fS )NFTrO  re   )rK   r   punctuation_assistanceis_layer_buttonr   is_prediction_keyrW  is_separator_cancellingr  r  _get_key_down_labelr  r  r  rB  r  r  r7  rN  )r;   r   r  r  afterr  
insert_nowr(  r  r  r  r  	caret_posr  s                 r<   r  z-PunctuatorDelayedSeparators._handle_key_event  s   
	!99++J""$ &&(!%J .;;=D%)
,,.)-&%%'!446
33C@D > >> .  ; ;; +#< $:#9   * 0 0 2I$:$@$@$BM"txx'C'C'E !i=&@&/=13D&D !7 ; ; = 3266!;62;;q@;5,0	 "i=&@)-J"	](B(1]Q5F(F)-J .2*)-&%-tY>>%ui77r>   c                 ~    | j                   j                  |      }||j                         }|| j                   |<   |S )a  
        Return the label key had before it was pressed down.
        Keys may change labels when modifiers are activated or released,
        but we need the same label before and after the press here.
        Else ":" turns to "." in Compact and requests capitalization.
        Multiple pressed keys may be in flight, due to multi-touch
        so keep the label per key.
        )r  getr  )r;   r   r  labels       r<   r  z/PunctuatorDelayedSeparators._get_key_down_label	  s?     %%))#.=MMOE).D!!#&r>   r@   r1  )r2  r3  r4  r5  r=   rB   r!  r   r   r   r   r   r  r  r   r>   r<   r   r   }  s<    &#3D=3&	2*G8Rr>   r   c                   ,    e Zd ZdZdZdZdZdZd Zd Z	y)r  z0 Word level information about found predictions FNc                 <    || _         || _        || _        d | _        y r@   )r  r  r  r  )r;   r  r  r  s       r<   r=   zWordInfo.__init__!  s    
	#r>   c           
          dj                  | j                  | j                  | j                  | j                  | j
                  | j                  | j                  | j                        S )NzG'{}' {}-{} unknown={} exact={} partial={} ignored={} spelling_errors={})	rj   r  r  r  unknownr  r  r  r  r:   s    r<   __str__zWordInfo.__str__'  sR    $$*F499+/::txx+/<<9I9I+/+=+=t||+/+?+?	%A	Ar>   )
r2  r3  r4  r5  r  r  r  r  r=   r  r   r>   r<   r  r    s$    :KMGO$Ar>   r  c                       e Zd ZdZdZd Zd Zd Zd Zd Z	d Z
d	 Zd
 Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Z	 ddZd Zd Zed        Zed        Zy)rH   zA Panel populated with correction and prediction keys at run-time _suggestion_c                     t        j                  |        d| _        d| _        d| _        d| _        d| _        d| _        g | _        y r!   )	r   r=   _correcions_expanded_first_prediction_more_predictions_requested_can_goto_previous_predictions_can_goto_next_predictions_next_predictions_increment_prev_predictions_stackr:   s    r<   r=   zWordListPanel.__init__7  sH    T"$)!!"+0(.3+*/'+,(')$r>   c                      y)Nre   r   r:   s    r<    get_max_non_expanded_correctionsz.WordListPanel.get_max_non_expanded_correctionsA  s    r>   c                     || _         y r@   r  )r;   r   s     r<   r   z WordListPanel.expand_correctionsD  s
    $*!r>   c                     | j                   S r@   r  r:   s    r<   r   z&WordListPanel.are_corrections_expandedG  s    (((r>   c                     | j                   S )z0 Has the more arrow been clicked at least once? )r  r:   s    r<   were_more_predictions_requestedz-WordListPanel.were_more_predictions_requestedJ  s    ///r>   c                     | j                   S )z# index of first visible prediction )r  r:   s    r<   r   z(WordListPanel.get_first_prediction_indexN  s    %%%r>   c                     | j                   j                  | j                         | xj                  | j                  z  c_        d| _        y)z! scroll suggestions to the right TN)r  rs  r  r   r  r:   s    r<   goto_next_predictionsz#WordListPanel.goto_next_predictionsR  s;    $$++D,B,BC$"B"BB+/(r>   c                 `    | j                   r"| j                   d   | _        | j                   d= yy)z  scroll suggestions to the left r  N)r  r  r:   s    r<   goto_previous_predictionsz'WordListPanel.goto_previous_predictionsX  s1    ''%)%A%A"%ED",,R0 (r>   c                 .    d| _         g | _        d| _        y)z reset scroll position r   FN)r  r  r  r:   s    r<   r   z#WordListPanel.goto_first_prediction^  s    !"')$+0(r>   c                     | j                   S r@   )r  r:   s    r<   can_goto_previous_predictionsz+WordListPanel.can_goto_previous_predictionsd  s    222r>   c                     | j                   S r@   )r  r:   s    r<   can_goto_next_predictionsz'WordListPanel.can_goto_next_predictionsg  s    ...r>   c                 r    |j                         j                  t        j                  j                  z  dz  S )N      Y@)get_initial_border_rectr<  rK   theme_settingskey_sizer   s     r<   _get_button_widthzWordListPanel._get_button_widthj  s5    --/11!!**+-23 	3r>   c                 p    t         j                  d   dz  dt         j                  j                  dz  z
  z  S Nr   r   g       @r  )rK   WORDLIST_BUTTON_SPACINGr  r  r:   s    r<   _get_button_spacingz!WordListPanel._get_button_spacingn  s8    --a0146((11E99; 	;r>   c                 p    t         j                  d   dz  dt         j                  j                  dz  z
  z  S r  )rK   WORDLIST_ENTRY_SPACINGr  r  r:   s    r<   _get_entry_spacingz WordListPanel._get_entry_spacingr  s8    ,,Q/!36((11E99; 	;r>   c                    t        | j                  g d            }t        | j                  dg            }t        | j                  g d            }| j                  }|dk(  rt        j                  j
                  nd}| j                  }||||z    }| j                  |||      }	|D ]#  }
|
j                  |
j                         |	v        % | j                  |||	      \  }}||z   }|r|d   j                  }|D ]	  }||_
         | j                  ||z   |z   |z          | j                  dkD  | _        | j                  t        |      z   t        |      k  | _        t        |      | _        |S )N)rO   
prediction
correctionexpand-corrections)previous-predictionsnext-predictionszpause-learninglanguagerJ  r   d   )r   find_idsr  rK   r   ro  _get_visible_buttonsset_visibleget_id_create_keyscolor_scheme	set_itemsr  r   r  r   )r;   r^  prediction_choicesfixed_backgroundfixed_buttonshideable_buttonsrh  max_choicesscrolled_prediction_choicesvisible_button_idsr   correction_keysprediction_keysr   r-  r   s                   r<   r   zWordListPanel.create_keysv  s    /= !> ?  /C.D EF /7 !8 9 &&z ii00" 	 &&uU[%89 	$
 %%&8:L&AC 	 ' 	FFv}}2DDE	F
 0902 	) 0(+88L 0#/ 0'$.$%'78 	9 ""Q& 	+ ""S%99"#$ 	' ,/+?(r>   c                    t         j                  j                         }d}d}||v }||v }|s|r|d d  }	|r|	j                  |       |r|	j                  |       | j	                  |||	      \  }
}|d d  }	t        |      t        |      k7  xr | j                   }|r| j                  r|s|	j                  |       |r|s|	j                  |       |	S |}	|	S )Nr$  r%  )rK   r  get_shown_wordlist_button_idsremover,  r   r  r  )r;   r^  r/  r4  enabled_button_idsprevious_predictions_idnext_predictions_idprevious_predictions_enablednext_predictions_enabledr5  r6  r7  rb   s                r<   r)  z"WordListPanel._get_visible_buttons  s%    ##AAC 	 #90 $'99 	%  #55 	! (# "4A!6+"))*AB'"))*=> !!"4"="46 -O_ "4A!6/*c2D.EE .---  ,44"))*AB'"))*=> "! "4!!r>   c                    g }g }| j                  d      }|r%|j                  }|j                         }|j                         }	g }
| j	                         }t        |      D ]`  }| j                  |      }|s|j                  s$| j                  |      }|	xj                  ||z   z  c_        |
j                  ||df       b t               j                  ||	j                               }|j                  |        | j                  ||	|||      \  }}|	xj                  |j                  z  c_        |	xj                  |j                  z  c_        | j!                         s| j#                  ||	||      }|
r|j                         }|
D ]  \  }}}|j                         }||_        |dk(  rF|j%                         |z
  |_        |xj                  |z  c_        |xj                  ||z   z  c_        n5|dk(  r0|j'                         |z
  |_        |xj                  ||z   z  c_        |j)                  |        ||fS )zm
        Dynamically create a variable number of buttons
        for word correction and prediction.
        rO   re   r  )_get_child_buttonr  get_rectr  r  reversedrR   r  r<  rs  r   calc_font_sizeget_sizer*  _create_correction_keysr;  r   _create_prediction_keysr  r  set_border_rect)r;   r^  r/  r5  r6  r7  rO   key_contextwordlist_rectrE  buttonsbutton_spacing	button_idr   button_width	font_size	used_rectrwalignrs                       r<   r,  zWordListPanel._create_keys  s&    ))*5"**K$--/M %%'D G!557N%&89 >	//	:fnn#'#9#9&#ALFFl^;;FNNFL!#<=>  	00dmmoNI   %7!78 ,,-?-18-8)E 'OY FFikk!FFFikk!F 002001C15{1:<   "'')3: 
./FL%	A&AC{ ggi,6,~ ==! hhj<7~ ==**1-
. //r>   c           	         |j                         }|j                         }d}| j                         sC||j                         j                  |j                  z
  z  }t        ||j                  dz        }t        |      dkD  }	d}
| j                  d      }|r+|	r)| j                  |      }
|xj                  |
|z   z  c_        | j                  d      }| j                         }|d| }| j                         r||d ng }| j                  ||||d|      \  }}g }g }|r|r|	rn|j                         }|j                         |_        |
|_        |j                  |       |j                  d       |xj                  |j                  z  c_        n|j                  d       | j                         r|j                         }n|j                         }|j                         }||j                  z
  |_        t!        d	|      }d|_        d|_        |j'                  |       |j                         }|j                         |z
  |z
  |_        ||z   |_        t!        d
|      }d|_        d|_        |j'                  |       |xj                  |z  c_        |r|j                         }|xj                  |j                  z  c_        |xj                  |j                  z  c_        | j                  ||||t        |            \  }}||z  }|xj                  |j                  z  c_        n|r|j                  d       ||z   |z   |fS )z-
        Create all correction keys.
        re   g?r   r#  r"  NTFcorrectionsbgpredictionsbg)r  rB  r   get_fullsize_rectr<  r=  r?  r   rA  r  r  _create_correction_choicesr  r;  rH  r*  r   	sensitive	scannablers  )r;   r^  rE  rO   rI  rO  choices_rectrJ  section_spacingshow_buttonrN  r   templateri  ra  expanded_choicesr   rP  exp_keysbg_keysrS  x_splitr   exp_rectexp_used_rects                            r<   rF  z%WordListPanel._create_correction_keys	  s   
 yy{ ))+,,.x99;==OOO!/=??S3HIO ,-1''(<=k11&9LNNl_<<N )),7 113$Ra(,,. .ab146 	
 ++G\,7,-x9 	i  "(A#//+AC&AC**1-&&t,KK133&K&&u- ,,.'--/#//+""$AACC-ACoq1C!CM!CMNN3""$A'')g%7ACO+ACoq1C!CM!CMNN3KK?*K  ',,.

ikk)


ikk)
334Dh4?47LB (-  }.""5)~()33r>   c                 H    t        | j                  |g            }|r|d   S y r   )r   r(  )r;   idr   s      r<   rA  zWordListPanel._get_child_buttonp	  s&    T]]B4()8Or>   Nc                 v   | j                         }| j                  ||||      \  }}	}
g }d\  }}t        |      D ]  \  }}|j                  }t	        |j
                  |z   |j                  |z   ||j                        }t        d|      }dt        |      z   |_
        | j                  |_        d|j                  dd i|_        ||_        t         j"                  |_        ||z   |_        |r|j(                  |_        |j+                  |       |||z   z  } |r||z  }|j-                         }||_        ||fS )zV
        Dynamically create a variable number of buttons for word correction.
                ri  r   r"  r   N)r  _fill_rect_with_choicesr  r<  r   r;  yr?  r   strrf  SUGGESTIONS_GROUPrV   r  labelsrO  r	   r   r   r   tooltiprs  r  )r;   ra  rE  rI  rO  start_indexr^  spacingbutton_infos	filled_upxendr   r;  rk  r  bir<  rS  r   rP  s                       r<   rX  z(WordListPanel._create_correction_choicesv	  s7    ))+(($YO 	&i 1|, 	EArA TVVaZ!Q7A"a.C!CF*CF..CIbhhqk*CJ%CM 00CH"QCH&..KKWA!	& LAIIK		Yr>   c           
         g }| j                         }| j                  ||||      \  }}}	|rSt        |      dz
  |z  }
|r@t        d |D              }t        d |D              }|j                  |z
  |
z
  }|r||z  nd}n|j                  |
z
  t        |	|
z
        z  }d\  }}t        |      D ]  \  }}|j                  }|r|j                  r||z  }t        dt        |      z   t        |j                  |z   |j                  |z   ||j                              }| j                  |_        d|j                   dd i|_        ||_        t&        j(                  |_        ||_        |j/                  |       |||z   z  } |S )	zV
        Dynamically create a variable number of buttons for word prediction.
        re   c              3   L   K   | ]  }|j                   s|j                    y wr@   r   r<  r  ru  s     r<   r  z8WordListPanel._create_prediction_keys.<locals>.<genexpr>	  s"      ,>R3599 -/DD ,>   "$c              3   L   K   | ]  }|j                   r|j                    y wr@   rx  ry  s     r<   r  z8WordListPanel._create_prediction_keys.<locals>.<genexpr>	  s"      )7",.II *, )7rz  g      ?rh  r!  r   N)r  rj  r   sumr<  floatr  r   r   rl  r   r;  rk  r?  rm  rV   r  rn  rO  r	   r   r   r   rs  )r;   ra  rJ  rI  rO  r   rq  rr  rs  rt  all_spacingslength_nonexpandableslength_expandableslength_targetscaler;  rk  r  ru  r<  r   s                        r<   rG  z%WordListPanel._create_prediction_keys	  s   
 ))+ ((-)4iA 	&i -1W<L ), ,>< ,> )>%%( )7 )7 &7"!.!6"7!-". * &(::/2 
 (//L8tl234
 DAq"<0 !2DD !BIIJA lSV3"=??Q#6+oo123 !22	"((1+.
 )$..C Q[ '!* r>   c                 h   | j                         }d}t        j                  d |      }g }d}	t        j                  d   dz  }
|j                  t        j                        }t        |      D ]  \  }}|j                  |
z
  |z  }| j                  |||      \  }}||z  |
z   }||j                  k\  }|s|j                  }||z   |j                  kD  rd}	 nR G d d      } |       }||_        ||_        ||_	        ||_        |d d  |_        |j!                  |       |||z   z  } |r||z  }||	|fS )Nri  Fr   r   Tc                       e Zd Zy)9WordListPanel._fill_rect_with_choices.<locals>.ButtonInfoN)r2  r3  r4  r   r>   r<   
ButtonInfor  	  s    r>   r  )r  r   get_pango_layoutrK   WORDLIST_LABEL_MARGINscale_log_to_canvas_xr   SCALEr  r<  
_ellipsizer?  label_widthr;  r   r  rs  )r;   ra  rE  rI  rO  rq  r;  pango_layoutrr  rs  marginsr  r  r#  	max_widthr  r  r<  r   r  ru  s                        r<   rj  z%WordListPanel._fill_rect_with_choices	  sR   ))+ //i@	..q1A511%++>"7+ 	IAv')U2Ifi@ E;e#g-A $&&[FFF 1utvv~ 	 B(BNBDBDBIQxBH#WA;	> LAY))r>   c                     |}t         j                  | |      }||kD  rAd}t        |d      D ]/  \  }}|d| dz   }t         j                  | |      }||kD  r ||fS |}1 ||fS )z. Shorten very long words and add an ellipsis. r   )r  Nz...)rH   _get_text_sizer  )r  r  r  ellipsized_textr<  r  cwts           r<   r  zWordListPanel._ellipsize
  s     ((t<y= A!$a0 1"&r(U"2"11,2AC	>!!  !!r>   c                 P    | j                  |d       | j                         \  }}|S r  )set_textrE  )r  r  r  _label_heights       r<   r  zWordListPanel._get_text_size
  s+    dB'%1%:%:%<"]r>   r  )r2  r3  r4  r5  rm  r=   r  r   r   r  r   r  r  r   r  r  r  r  r  r   r)  r,  rF  rA  rX  rG  rj  r6  r  r  r   r>   r<   rH   rH   0  s    K '*+)0&0113/3;;0d-"^<0|Y4v <@%N8t-*^ " "$  r>   rH   c                   P    e Zd ZdZddZd Zd Zd Zd ZddZ		 	 dd	Z
d
 Zd Zy)r-   z= Notify of and recover from errors when loading user models. Nc                 L    |rt        j                  |      | _        y d | _        y r@   )weakrefref	_keyboardr;   keyboards     r<   r=   zModelErrorRecovery.__init__#
  s    2:X.r>   c                 <    | j                   r| j                         S y r@   )r  r:   s    r<   get_keyboardzModelErrorRecovery.get_keyboard&
  s    >>>>## r>   c                 @    | j                         }|r|j                  S y r@   )r  r0   r  s     r<   get_wpenginezModelErrorRecovery.get_wpengine*
  s"    $$&%%%r>   c                 @    | j                         }|r|j                  S y r@   )r  _model_cache)r;   wpengines     r<   get_model_cachez"ModelErrorRecovery.get_model_cache0
  s"    $$&(((r>   c                 R   | j                         }| j                         }d}|j                  D ]c  }|j                  |      s|j	                  |      }|j                  |      }|j                  sD|xs | j                  ||j                        }e |r|j                          y y r   )
r  r  modelsis_user_lmid	get_modelget_filename
load_error_report_load_errorload_error_msgr  )r;   r  cacheretrylmidrv   filenames          r<   ry   z ModelErrorRecovery.report_errors6
  s    $$&$$&OO 	PD!!$'- --d3##! P//%:N:NO 	P KKM r>   c                 ^   d}| j                         }|r|j                         j                  nd}| j                  |      }| j	                  |      }|r G d d      }	 |	       }
t
        j                  j                  |      rdt        d|      }t        |d      }|s| j                  ||d	      nd}|r/	 t        j                  ||       t        j                  ||       d}|S t        d||      }t        |d      }|s| j                  ||       	 t        j                  ||       |S # t        $ r3}
j                  d
j!                  t#        |                   Y d}~d}~ww xY w# t        $ r4}
j                  dj!                  t#        |                   Y d}~|S d}~ww xY w)a   
        Handle model load failure.

        Doctests:
        >>> import tempfile
        >>> import subprocess
        >>> import glob
        >>> from os.path import basename, join
        >>> td = tempfile.TemporaryDirectory(prefix="test_onboard_")
        >>> tdir = td.name
        >>> fn = os.path.join(tdir, "en_US.lm")
        >>> mc = ModelErrorRecovery()

        >>> def touch(fn):
        ...     with open(fn, mode="w") as f: pass
        >>>
        >>> def test(fn):
        ...    mc._report_load_error(fn, "Failed to load model", True)
        ...    for f in sorted(f for f in glob.glob(join(tdir, "*"))):
        ...        print(repr(basename(f)))
        ...        os.remove(f)

        # If there is no backup, the old model shall be removed and saved
        # under the broken name.
        >>> touch(fn)
        >>> test(fn)    # doctest: +ELLIPSIS
        'en_US.lm.broken-..._001'

        # Test rename failure
        >>> test(fn)    # doctest: +ELLIPSIS
        Failed to rename broken model: ...

        # If there is a backup, the old model shall be saved under the broken
        # name and a copy of the backup becomes the new model.
        >>> touch(fn)
        >>> touch(mc.get_backup_filename(fn))
        >>> test(fn)    # doctest: +ELLIPSIS
        'en_US.lm'
        'en_US.lm.bak'
        'en_US.lm.broken-..._001'

        # Test rename failure
        >>> touch(mc.get_backup_filename(fn))
        >>> test(fn)    # doctest: +ELLIPSIS
        Failed to revert to backup model: ...
        FNc                       e Zd Zd Zy)5ModelErrorRecovery._report_load_error.<locals>.Loggerc                     t        |       y r@   )print)r;   r"  s     r<   errorz;ModelErrorRecovery._report_load_error.<locals>.Logger.error~
  s	    !Hr>   N)r2  r3  r4  r  r   r>   r<   Loggerr  }
  s    r>   r  z<big>Onboard failed to open learned word suggestions.</big>

The error was:
<tt>{}</tt>

Revert word suggestions to the last backup (recommended)?T)preserve_tagsyes_no)rK  z$Failed to revert to backup model: {}z<big>Onboard failed to open learned word suggestions.</big>

The error was:
<tt>{}</tt>

The suggestions have to be reset, but the erroneous file will remain at 
'{}'z!Failed to rename broken model: {})r  r  _windowget_backup_filenameget_broken_filenameospathexists_formatr   _show_dialogrenameshutilr  OSErrorr  rj   r   )r;   r  msg
_test_moder  r  parentbackup_filenamebroken_filenamer  rh   markupreplyexs                 r<   r  z%ModelErrorRecovery._report_load_errorG
  s   ^ $$&7?))+33T228<228<  hG77>>/*L F #6>F! %%ffh%G'+  <IIh8KK: "  ;
 _&F #6>F!!&&18		(O4 )  <MM"H#)6+b/#:< <<"  8A%vk"o68 88s0   9,D0 E/ 0	E,9)E''E,/	F,8)F''F,c                    | j                         }|dk(  rt        j                  j                  }nt        j                  j                  }|dk(  rt        j
                  j                  }nt        j
                  j                  }t        j                  j                  }t        j                  ||      }|j                  |       |r|j                  |       t        j                         r)|j                  t        j                  j                         |r|j!                          |j#                         }|j%                          |r|j'                          |t        j(                  j*                  k(  S )Nquestionr  )message_typerK  )r  r   MessageTypeQUESTIONERRORButtonsTypeYES_NOOKMessageDialog
set_markupset_transient_forrK   is_force_to_topset_positionWindowPositionCENTERr  rundestroyr  ResponseTypeYES)r;   r  r  r  rK  r  dialogresponses           r<   r  zModelErrorRecovery._show_dialog
  s   $$&:%??33L??00Lhoo,,Goo((G//""+24&!$$V, !!# 2 2 9 9:--/::<,,.3++////r>   c                 ,    t        j                  |      S r@   )r   r  r;   r  s     r<   r  z&ModelErrorRecovery.get_backup_filename
      --h77r>   c                 ,    t        j                  |      S r@   )r   r  r  s     r<   r  z&ModelErrorRecovery.get_broken_filename
  r  r>   r@   )F)Nr  ok)r2  r3  r4  r5  r=   r  r  r  ry   r  r  r  r  r   r>   r<   r-   r-    
  s>    GE$"^@ +/37 0D88r>   r-   )@
__future__r   r   r   r  r  r7  r  r  Onboard.Versionr   gi.repositoryr   r   logging	getLoggerr2  rh   Onboard.pypredictr<  Onboardr	   Onboard.TextContextr
   r   Onboard.TextChangesr   r   Onboard.SpellCheckerr   Onboard.LanguageSupportr   Onboard.Layoutr   Onboard.AtspiStateTrackerr   Onboard.WPEnginer   r   Onboard.utilsr   r   r   Onboard.Timerr   r   r   Onboard.KeyGtkr   r   Onboard.KeyboardPopupsr   Onboard.Configr   rK   r   r:  r%   r  r)   r   r  rH   r-   r   r>   r<   <module>r     s   ( B A 	  	   /   $ 
'

H
% % / A ; 2 0 1 7 ? F F @ @ : ; !	^ ^B)N NbM)} M)`?0 ?0D@DJ @DFY* YxA A.mK m`m8 m8r>   