diff options
Diffstat (limited to 'lib/editor/spell_check.tcl')
-rw-r--r-- | lib/editor/spell_check.tcl | 1325 |
1 files changed, 1325 insertions, 0 deletions
diff --git a/lib/editor/spell_check.tcl b/lib/editor/spell_check.tcl new file mode 100644 index 0000000..7759607 --- /dev/null +++ b/lib/editor/spell_check.tcl @@ -0,0 +1,1325 @@ +#!/usr/bin/tclsh +# Part of MCU 8051 IDE ( http://mcu8051ide.sf.net ) + +############################################################################ +# Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 by Martin Ošmera # +# martin.osmera@gmail.com # +# # +# This program is free software; you can redistribute it and#or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation; either version 2 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program; if not, write to the # +# Free Software Foundation, Inc., # +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # +############################################################################ + +# >>> File inclusion guard +if { ! [ info exists _SPELL_CHECK_TCL ] } { +set _SPELL_CHECK_TCL _ +# <<< File inclusion guard + +# -------------------------------------------------------------------------- +# DESCRIPTION +# Spell checker interface used by the source code editor. +# +# Used spell checker is Hunspell. +# +# +# COMMUNICATION CONNECTIONS WITH HUNSPELL: +# ----------------------------------------- +# +# +-------------------+ (4) +----------+ (5) +------------------+ +# | receive_and_print | | | Hunspell | | | external_command | +# | (RAP) | ---> | | ---> | | +# +-------------------+ +----------+ +------------------+ +# ^ |--(2) |--3 +# (1)--| v v +# +--------------------------------------------------------+ +# | spell_check | +# +--------------------------------------------------------+ +# +# 1: Send a word to check along with commands to execute in case of correct and wrong spelling +# 2: Receive the identifier for IPC with the RAP (variable spellchecker_RAP_ID) +# 3: Receive response from the Hunspell via IPC +# 4: Send a word to check to the Hunspell via a pipe +# 5: Receive response from the Hunspell via a pipe +# +# -------------------------------------------------------------------------- + +## COMMON +common spellchecker_enabled 0 ;# Bool: Flag spell checking enabled +common spellchecker_dictionary {} ;# String: Dictionary to use (e.g. en_US or cs_CZ) +common spellchecker_process_pid [list] ;# List of Ints: Process identifiers of the spell checker and support processes +common spellchecker_command_LIFO [list] ;# List: LIFO for commands invoked by spell checker {correct_spelling_cmd wrong_spelling_cmd} +common spellchecker_RAP_ID {} ;# String: Application name of ``receive_and_print'' for IPC +common spellchecker_attempts_to_restart 0 ;# Int: Number of failed attempts to restart the spell checker process +common spellchecker_started_flag ;# None: When this variable is set that means that the spell checker process has been started +common spellchecker_start_failed 0 ;# Bool: Flag spellchecker_started_flag was set but the spell checker process was not actually started +common spellchecker_start_timer {} ;# AfterTimer: Watch dog timer for start of of the spell checker process +common available_dictionaries [list] ;# List of Strings: Dictionaries available to the Hunspell +common hunspell_process {} ;# Channel: Hunspell process invoked by command open in order to gain list of dictionaries + +## PRIVATE +private variable spellcheck_line_pre {} ;# String: Content of the line where change_detected_pre was performed +private variable spellcheck_line_number {} ;# Int: Number of the last line where change_detected_pre was performed, see spellcheck_check_all +private variable spellcheck_lock 0 ;# Bool: Inhibit method ``spellcheck_check_all'' + +## COMMON + +## List: Language codes and language names according to: ISO-639-1 + # Format: + # { + # { Language_Name Language_Code } + # ... + # } +common LANGUAGE_CODES_AND_NAMES { + {{Abkhazian} {ab}} {{Afar} {aa}} + {{Afrikaans} {af}} {{Akan} {ak}} + {{Albanian} {sq}} {{Amharic} {am}} + {{Arabic} {ar}} {{Aragonese} {an}} + {{Armenian} {hy}} {{Assamese} {as}} + {{Avaric} {av}} {{Avestan} {ae}} + {{Aymara} {ay}} {{Azerbaijani} {az}} + {{Bambara} {bm}} {{Bashkir} {ba}} + {{Basque} {eu}} {{Belarusian} {be}} + {{Bengali} {bn}} {{Bihari languages} {bh}} + {{Bislama} {bi}} {{Bokmål, Norwegian} {nb}} + {{Bosnian} {bs}} {{Breton} {br}} + {{Bulgarian} {bg}} {{Burmese} {my}} + {{Castilian} {es}} {{Catalan} {ca}} + {{Central Khmer} {km}} {{Chamorro} {ch}} + {{Chechen} {ce}} {{Chewa} {ny}} + {{Chichewa} {ny}} {{Chinese} {zh}} + {{Chuang} {za}} {{Church Slavic} {cu}} + {{Church Slavonic} {cu}} {{Chuvash} {cv}} + {{Cornish} {kw}} {{Corsican} {co}} + {{Cree} {cr}} {{Croatian} {hr}} + {{Czech} {cs}} {{Danish} {da}} + {{Dhivehi} {dv}} {{Divehi} {dv}} + {{Dutch} {nl}} {{Dzongkha} {dz}} + {{English} {en}} {{Esperanto} {eo}} + {{Estonian} {et}} {{Ewe} {ee}} + {{Faroese} {fo}} {{Fijian} {fj}} + {{Finnish} {fi}} {{Flemish} {nl}} + {{French} {fr}} {{Fulah} {ff}} + {{Gaelic} {gd}} {{Galician} {gl}} + {{Ganda} {lg}} {{Georgian} {ka}} + {{German} {de}} {{Gikuyu} {ki}} + {{Greek, Modern} {el}} {{Greenlandic} {kl}} + {{Guarani} {gn}} {{Gujarati} {gu}} + {{Haitian} {ht}} {{Haitian Creole} {ht}} + {{Hausa} {ha}} {{Hebrew} {he}} + {{Herero} {hz}} {{Hindi} {hi}} + {{Hiri Motu} {ho}} {{Hungarian} {hu}} + {{Icelandic} {is}} {{Ido} {io}} + {{Igbo} {ig}} {{Indonesian} {id}} + {{Interlingue} {ie}} {{Inuktitut} {iu}} + {{Inupiaq} {ik}} {{Irish} {ga}} + {{Italian} {it}} {{Japanese} {ja}} + {{Javanese} {jv}} {{Kalaallisut} {kl}} + {{Kannada} {kn}} {{Kanuri} {kr}} + {{Kashmiri} {ks}} {{Kazakh} {kk}} + {{Kikuyu} {ki}} {{Kinyarwanda} {rw}} + {{Kirghiz} {ky}} {{Komi} {kv}} + {{Kongo} {kg}} {{Korean} {ko}} + {{Kuanyama} {kj}} {{Kurdish} {ku}} + {{Kwanyama} {kj}} {{Kyrgyz} {ky}} + {{Lao} {lo}} {{Latin} {la}} + {{Latvian} {lv}} {{Letzeburgesch} {lb}} + {{Limburgan} {li}} {{Limburger} {li}} + {{Limburgish} {li}} {{Lingala} {ln}} + {{Lithuanian} {lt}} {{Luba-Katanga} {lu}} + {{Luxembourgish} {lb}} {{Macedonian} {mk}} + {{Malagasy} {mg}} {{Malay} {ms}} + {{Malayalam} {ml}} {{Maldivian} {dv}} + {{Maltese} {mt}} {{Manx} {gv}} + {{Maori} {mi}} {{Marathi} {mr}} + {{Marshallese} {mh}} {{Moldavian} {ro}} + {{Moldovan} {ro}} {{Mongolian} {mn}} + {{Nauru} {na}} {{Navaho} {nv}} + {{Navajo} {nv}} {{Ndebele, North} {nd}} + {{Ndebele, South} {nr}} {{Ndonga} {ng}} + {{Nepali} {ne}} {{North Ndebele} {nd}} + {{Northern Sami} {se}} {{Norwegian} {no}} + {{Norwegian Bokmål} {nb}} {{Norwegian Nynorsk} {nn}} + {{Nuosu} {ii}} {{Nyanja} {ny}} + {{Nynorsk, Norwegian} {nn}} {{Occidental} {ie}} + {{Occitan} {oc}} {{Ojibwa} {oj}} + {{Old Bulgarian} {cu}} {{Old Church Slavonic} {cu}} + {{Old Slavonic} {cu}} {{Oriya} {or}} + {{Oromo} {om}} {{Ossetian} {os}} + {{Ossetic} {os}} {{Pali} {pi}} + {{Panjabi} {pa}} {{Pashto} {ps}} + {{Persian} {fa}} {{Polish} {pl}} + {{Portuguese} {pt}} {{Punjabi} {pa}} + {{Pushto} {ps}} {{Quechua} {qu}} + {{Romanian} {ro}} {{Romansh} {rm}} + {{Rundi} {rn}} {{Russian} {ru}} + {{Samoan} {sm}} {{Sango} {sg}} + {{Sanskrit} {sa}} {{Sardinian} {sc}} + {{Scottish Gaelic} {gd}} {{Serbian} {sr}} + {{Shona} {sn}} {{Sichuan Yi} {ii}} + {{Sindhi} {sd}} {{Sinhala} {si}} + {{Sinhalese} {si}} {{Slovak} {sk}} + {{Slovenian} {sl}} {{Somali} {so}} + {{Sotho, Southern} {st}} {{South Ndebele} {nr}} + {{Spanish} {es}} {{Sundanese} {su}} + {{Swahili} {sw}} {{Swati} {ss}} + {{Swedish} {sv}} {{Tagalog} {tl}} + {{Tahitian} {ty}} {{Tajik} {tg}} + {{Tamil} {ta}} {{Tatar} {tt}} + {{Telugu} {te}} {{Thai} {th}} + {{Tibetan} {bo}} {{Tigrinya} {ti}} + {{Tonga} {to}} {{Tsonga} {ts}} + {{Tswana} {tn}} {{Turkish} {tr}} + {{Turkmen} {tk}} {{Twi} {tw}} + {{Uighur} {ug}} {{Ukrainian} {uk}} + {{Urdu} {ur}} {{Uyghur} {ug}} + {{Uzbek} {uz}} {{Valencian} {ca}} + {{Venda} {ve}} {{Vietnamese} {vi}} + {{Volapük} {vo}} {{Walloon} {wa}} + {{Welsh} {cy}} {{Western Frisian} {fy}} + {{Wolof} {wo}} {{Xhosa} {xh}} + {{Yiddish} {yi}} {{Yoruba} {yo}} + {{Zhuang} {za}} {{Zulu} {zu}} +} + +## List: Country codes with names of their flags file in directory ``${::ROOT_DIRNAME}/icons/flag/'' + # Format: + # { + # { Country_Name Country_Code Flag_File_Name_Without_Extension } + # ... + # } +common COUNTRY_CODES_AND_FLAGS { + {{Afghanistan} AF Afghanistan} + {{Åland Islands} AX {}} + {{Albania} AL Albania} + {{Algeria} DZ Algeria} + {{American Samoa} AS American_Samoa} + {{Andorra} AD Andorra} + {{Angola} AO Angola} + {{Anguilla} AI Anguilla} + {{Antarctica} AQ {}} + {{Antigua And Barbuda} AG Antigua_and_Barbuda} + {{Argentina} AR Argentina} + {{Armenia} AM Armenia} + {{Aruba} AW Aruba} + {{Australia} AU Australia} + {{Austria} AT Austria} + {{Azerbaijan} AZ Azerbaijan} + + {{Bahamas} BS Bahamas} + {{Bahrain} BH Bahrain} + {{Bangladesh} BD Bangladesh} + {{Barbados} BB Barbados} + {{Belarus} BY Belarus} + {{Belgium} BE Belgium} + {{Belize} BZ Belize} + {{Benin} BJ Benin} + {{Bermuda} BM Bermuda} + {{Bhutan} BT Bhutan} + {{Bolivia, Plurinational State Of} BO Bolivia} + {{Bosnia And Herzegovina} BA Bosnia} + {{Botswana} BW Botswana} + {{Bouvet Island} BV {}} + {{Brazil} BR Brazil} + {{British Indian Ocean Territory} IO {}} + {{Brunei Darussalam} BN Brunei} + {{Bulgaria} BG Bulgaria} + {{Burkina Faso} BF Burkina_Faso} + {{Burundi} BI Burundi} + + {{Cambodia} KH Cambodia} + {{Cameroon} CM Cameroon} + {{Canada} CA Canada} + {{Cape Verde} CV Cape_Verde} + {{Cayman Islands} KY Cayman_Islands} + {{Central African Republic} CF Central_African_Republic} + {{Chad} TD Chad} + {{Chile} CL Chile} + {{China} CN China} + {{Christmas Island} CX Christmas_Island} + {{Cocos (Keeling) Islands} CC {}} + {{Colombia} CO Colombia} + {{Comoros} KM Comoros} + {{Congo} CG Republic_of_the_Congo} + {{Congo, The Democratic Republic Of The} CD Democratic_Republic_of_the_Congo} + {{Cook Islands} CK Cook_Islands} + {{Costa Rica} CR Costa_Rica} + {{Côte D'Ivoire} CI Cote_dIvoire} + {{Croatia} HR Croatia} + {{Cuba} CU Cuba} + {{Cyprus} CY Cyprus} + {{Czech Republic} CZ Czech_Republic} + + {{Denmark} DK Denmark} + {{Djibouti} DJ Djibouti} + {{Dominica} DM Dominica} + {{Dominican Republic} DO Dominican_Republic} + + {{Ecuador} EC Ecuador} + {{Egypt} EG Egypt} + {{El Salvador} SV El_Salvador} + {{Equatorial Guinea} GQ Equatorial_Guinea} + {{Eritrea} ER Eritrea} + {{Estonia} EE Estonia} + {{Ethiopia} ET Ethiopia} + + {{Falkland Islands (Malvinas)} FK Falkland_Islands} + {{Faroe Islands} FO Faroe_Islands} + {{Fiji} FJ Fiji} + {{Finland} FI Finland} + {{France} FR France} + {{French Guiana} GF {}} + {{French Polynesia} PF French_Polynesia} + {{French Southern Territories} TF {}} + + {{Gabon} GA Gabon} + {{Gambia} GM Gambia} + {{Georgia} GE Georgia} + {{Germany} DE Germany} + {{Ghana} GH Ghana} + {{Gibraltar} GI Gibraltar} + {{Greece} GR Greece} + {{Greenland} GL Greenland} + {{Grenada} GD Grenada} + {{Guadeloupe} GP {}} + {{Guam} GU Guam} + {{Guatemala} GT Guatemala} + {{Guernsey} GG {}} + {{Guinea} GN Guinea} + {{Guinea-Bissau} GW Guinea_Bissau} + {{Guyana} GY Guyana} + + {{Haiti} HT Haiti} + {{Heard Island And Mcdonald Islands} HM {}} + {{Holy See (Vatican City State)} VA {}} + {{Honduras} HN Honduras} + {{Hong Kong} HK Hong_Kong} + {{Hungary} HU Hungary} + + {{Iceland} IS Iceland} + {{India} IN India} + {{Indonesia} ID Indonesia} + {{Iran, Islamic Republic Of} IR Iran} + {{Iraq} IQ Iraq} + {{Ireland} IE Ireland} + {{Isle Of Man} IM {}} + {{Israel} IL Israel} + {{Italy} IT Italy} + + {{Jamaica} JM Jamaica} + {{Japan} JP Japan} + {{Jersey} JE {}} + {{Jordan} JO Jordan} + + {{Kazakhstan} KZ Kazakhstan} + {{Kenya} KE Kenya} + {{Kiribati} KI Kiribati} + {{Korea, Democratic People'S Republic Of} KP North_Korea} + {{Korea, Republic Of} KR South_Korea} + {{Kuwait} KW Kuwait} + {{Kyrgyzstan} KG Kyrgyzstan} + + {{Lao People'S Democratic Republic} LA Laos} + {{Latvia} LV Latvia} + {{Lebanon} LB Lebanon} + {{Lesotho} LS Lesotho} + {{Liberia} LR Liberia} + {{Libyan Arab Jamahiriya} LY Libya} + {{Liechtenstein} LI Liechtenstein} + {{Lithuania} LT Lithuania} + {{Luxembourg} LU Luxembourg} + + {{Macao} MO Macao} + {{Macedonia, The Former Yugoslav Republic Of} MK Macedonia} + {{Madagascar} MG Madagascar} + {{Malawi} MW Malawi} + {{Malaysia} MY Malaysia} + {{Maldives} MV Maldives} + {{Mali} ML Mali} + {{Malta} MT Malta} + {{Marshall Islands} MH Marshall_Islands} + {{Martinique} MQ Martinique} + {{Mauritania} MR Mauritania} + {{Mauritius} MU Mauritius} + {{Mayotte} YT {}} + {{Mexico} MX Mexico} + {{Micronesia, Federated States Of} FM Micronesia} + {{Moldova, Republic Of} MD Moldova} + {{Monaco} MC Monaco} + {{Mongolia} MN Mongolia} + {{Montenegro} ME {}} + {{Montserrat} MS Montserrat} + {{Morocco} MA Morocco} + {{Mozambique} MZ Mozambique} + {{Myanmar} MM Myanmar} + + {{Namibia} NA Namibia} + {{Nauru} NR Nauru} + {{Nepal} NP Nepal} + {{Netherlands} NL Netherlands} + {{Netherlands Antilles} AN Netherlands_Antilles} + {{New Caledonia} NC {}} + {{New Zealand} NZ New_Zealand} + {{Nicaragua} NI Nicaragua} + {{Niger} NE Niger} + {{Nigeria} NG Nigeria} + {{Niue} NU Niue} + {{Norfolk Island} NF Norfolk_Island} + {{Northern Mariana Islands} MP {}} + {{Norway} NO Norway} + + {{Oman} OM Oman} + + {{Pakistan} PK Pakistan} + {{Palau} PW Palau} + {{Palestinian Territory, Occupied} PS {}} + {{Panama} PA Panama} + {{Papua New Guinea} PG Papua_New_Guinea} + {{Paraguay} PY Paraguay} + {{Peru} PE Peru} + {{Philippines} PH Philippines} + {{Pitcairn} PN Pitcairn_Islands} + {{Poland} PL Poland} + {{Portugal} PT Portugal} + {{Puerto Rico} PR Puerto_Rico} + + {{Qatar} QA Qatar} + + {{Réunion} RE {}} + {{Romania} RO Romania} + {{Russian Federation} RU Russian_Federation} + {{Rwanda} RW Rwanda} + + {{Saint Barthélemy} BL {}} + {{Saint Helena, Ascension And Tristan Da Cunha} SH {}} + {{Saint Kitts And Nevis} KN Saint_Kitts_and_Nevis} + {{Saint Lucia} LC Saint_Lucia} + {{Saint Martin} MF {}} + {{Saint Pierre And Miquelon} PM Saint_Pierre} + {{Saint Vincent And The Grenadines} VC Saint_Vicent_and_the_Grenadines} + {{Samoa} WS Samoa} + {{San Marino} SM San_Marino} + {{Sao Tome And Principe} ST Sao_Tome_and_Principe} + {{Saudi Arabia} SA Saudi_Arabia} + {{Senegal} SN Senegal} + {{Serbia} RS {}} + {{Seychelles} SC Seychelles} + {{Sierra Leone} SL Sierra_Leone} + {{Singapore} SG Singapore} + {{Slovakia} SK Slovakia} + {{Slovenia} SI Slovenia} + {{Solomon Islands} SB Soloman_Islands} + {{Somalia} SO Somalia} + {{South Africa} ZA South_Africa} + {{South Georgia And The South Sandwich Islands} GS South_Georgia} + {{Spain} ES Spain} + {{Sri Lanka} LK Sri_Lanka} + {{Sudan} SD Sudan} + {{Suriname} SR Suriname} + {{Svalbard And Jan Mayen} SJ {}} + {{Swaziland} SZ Swaziland} + {{Sweden} SE Sweden} + {{Switzerland} CH Switzerland} + {{Syrian Arab Republic} SY Syria} + + {{Taiwan, Province Of China} TW Taiwan} + {{Tajikistan} TJ Tajikistan} + {{Tanzania, United Republic Of} TZ Tanzania} + {{Thailand} TH Thailand} + {{Timor-Leste} TL Timor-Leste} + {{Togo} TG Togo} + {{Tokelau} TK {}} + {{Tonga} TO Tonga} + {{Trinidad And Tobago} TT Trinidad_and_Tobago} + {{Tunisia} TN Tunisia} + {{Turkey} TR Turkey} + {{Turkmenistan} TM Turkmenistan} + {{Turks And Caicos Islands} TC Turks_and_Caicos_Islands} + {{Tuvalu} TV Tuvalu} + + {{Uganda} UG Uganda} + {{Ukraine} UA Ukraine} + {{United Arab Emirates} AE UAE} + {{United Kingdom} GB United_Kingdom} + {{United States} US United_States_of_America} + {{United States Minor Outlying Islands} UM {}} + {{Uruguay} UY Uruguay} + {{Uzbekistan} UZ Uzbekistan} + + {{Vanuatu} VU Vanuatu} + {{Vatican City State} VA Vatican_City} + {{Venezuela, Bolivarian Republic Of} VE Venezuela} + {{Viet Nam} VN Vietnam} + {{Virgin Islands, British} VG British_Virgin_Islands} + {{Virgin Islands, U.S.} VI US_Virgin_Islands} + + {{Wallis And Futuna} WF Wallis_and_Futuna} + {{Western Sahara} EH {}} + + {{Yemen} YE Yemen} + {{Zambia} ZM Zambia} + {{Zimbabwe} ZW Zimbabwe} +} + +## Kill spell checker and its support processes + # @return void +proc kill_spellchecker_process {} { + # Reset some class variables + set ::Editor::spellchecker_RAP_ID {} + set ::Editor::spellchecker_command_LIFO [list] + + # Abort if the spell checker process is not running + if {${::Editor::spellchecker_process_pid} == {}} { + return + } + + # Kill the spell checker and its support processes + foreach pid ${::Editor::spellchecker_process_pid} { + if {$pid == [pid] || $pid == 0} { + continue + } + catch { + exec -- kill $pid 2>/dev/null + } + } + set ::Editor::spellchecker_process_pid {} +} + +## Restart the spell checker process with new new configuration + # @return void +proc restart_spellchecker_process {} { + # This function was not yet ported to MS Windows + if {$::MICROSOFT_WINDOWS} { + return + } + + kill_spellchecker_process + + if {[lsearch -ascii -exact ${::Editor::available_dictionaries} ${::Editor::spellchecker_dictionary}] == -1} { + set ::Editor::spellchecker_enabled 0 + set ::Editor::spellchecker_dictionary {} + } else { + start_spellchecker_process + wait_for_spellchecker_process + } +} + +## Start the spell checker (Hunspell) and its support processes + # @return void +proc start_spellchecker_process {} { + # Abort if either the feature is disabled or the Hunspell is not available + if {!${::Editor::spellchecker_enabled} || !${::PROGRAM_AVAILABLE(hunspell)}} { + return + } + # This function was not yet ported to MS Windows + if {$::MICROSOFT_WINDOWS} { + return + } + + # Start watch dog timer + set ::Editor::spellchecker_start_timer [after 10000 { + set ::Editor::spellchecker_start_failed 1 + set ::Editor::spellchecker_started_flag 1 + }] + + # Attempt to start the processes + if {[catch { + set ::Editor::spellchecker_process_pid [exec -- \ + tclsh ${::LIB_DIRNAME}/receive_and_print.tcl \ + [tk appname] \ + ::Editor::set_spellchecker_RAP_ID \ + | hunspell \ + -a \ + -i utf8 \ + -d ${::Editor::spellchecker_dictionary} \ + | tclsh ${::LIB_DIRNAME}/external_command.tcl \ + [tk appname] \ + ::Editor::spellchecker_exit_callback \ + ::Editor::spellchecker_receive_response \ + & \ + ] + }]} then { + # FAILURE + set ::Editor::spellchecker_start_failed 1 + set ::Editor::spellchecker_started_flag 1 + } +} + +## Wait until the spell checker (Hunspell) and its support processes are started + # @return void +proc wait_for_spellchecker_process {} { + # Abort if either the feature is disabled or the Hunspell is not available + if {!${::Editor::spellchecker_enabled} || !${::PROGRAM_AVAILABLE(hunspell)}} { + return + } + # This function was not yet ported to MS Windows + if {$::MICROSOFT_WINDOWS} { + return + } + + # Wait until the spell checker (Hunspell) and its support processes are started + vwait ::Editor::spellchecker_started_flag + unset ::Editor::spellchecker_started_flag + + # Stop the watch dog timer + catch { + after cancel ${::Editor::spellchecker_start_timer} + } + + # Handle spellchecker start-up failure + if {${::Editor::spellchecker_start_failed}} { + # Set some class variables + set ::Editor::spellchecker_RAP_ID {} + set ::Editor::spellchecker_enabled 0 + set ::Editor::spellchecker_start_failed 0 + + # Destroy the splash screen if displayed + if {[winfo exists .splash]} { + destroy .splash + } + + # Display graphical error message + tk_messageBox \ + -parent . \ + -type ok \ + -icon error \ + -title [mc "Hunspell error"] \ + -message [mc "Unable to start the spell checker. Please try to re-install the hunspell. Spell checking function will not be available"] + } +} + +## Receive the identifier for IPC with the RAP + # @parm String id - Appname of the receive_and_print process + # @return void +proc set_spellchecker_RAP_ID {id} { + set ::Editor::spellchecker_RAP_ID $id +} + +## Handle Hunspell process termination + # + # It is assumed that the process terminates only on some error condition or + # on an explicit request for termination. Aim of this method is attempt to + # restart the Hunspell process and its support processes if it crashed for any + # reason. + # + # @parm List args - Anything, it doesn't matter + # @return void +proc spellchecker_exit_callback {args} { + # Abort if the termination was intentional + if {${::Editor::spellchecker_RAP_ID} == {}} { + return + } + # This function was not yet ported to MS Windows + if {$::MICROSOFT_WINDOWS} { + return + } + + set ::Editor::spellchecker_RAP_ID {} + puts stderr "Spell checker process exited -- attempting to restart" + + # Attempt to restart + incr ::Editor::spellchecker_attempts_to_restart + if {${::Editor::spellchecker_attempts_to_restart} < 10} { + start_spellchecker_process + } else { + puts stderr "Attempt to restart failed, to many attempts -- aborting" + set spellchecker_attempts_to_restart 0 + } +} + +## Receive response from the Hunspell + # @parm List args - One line of the response + # @return void +proc spellchecker_receive_response {args} { + # This function was not yet ported to MS Windows + if {$::MICROSOFT_WINDOWS} { + return + } + + # We are interested only in the first field of the response + set response [string trim [lindex $args 0]] + + # Handle the initial response (sent once the Hunspell is started) + if {[lindex $response 0] == {@(#)}} { + set spellchecker_command_LIFO [list] + set ::Editor::spellchecker_started_flag 1 + return + } + + # Decide what to do with the response + switch -- $response { + {} { ;# Empty response -- means nothing + } + {*} { ;# Word is correct + catch { + eval [lindex $spellchecker_command_LIFO {0 0}] + } + set spellchecker_command_LIFO [lreplace $spellchecker_command_LIFO 0 0] + } + default { ;# Everything else + catch { + eval [lindex $spellchecker_command_LIFO {0 1}] + } + set spellchecker_command_LIFO [lreplace $spellchecker_command_LIFO 0 0] + } + } +} + +## Send a word to the Hunspell process for evaluation + # @parm String word - Work to check for correct spelling + # @parm String wrong_command = {} - Command to execute here if the word is badly spelled + # @parm String correct_command = {} - Command to execute here if the word is correctly spelled + # @return void +proc spellchecker_check_word {word {wrong_command {}} {correct_command {}}} { + # This function was not yet ported to MS Windows + if {$::MICROSOFT_WINDOWS} { + return + } + + # Abort if receive and print process has not been initialized + if {${::Editor::spellchecker_RAP_ID} == {}} { + return + } + + # Append command to their queue + lappend spellchecker_command_LIFO [list $correct_command $wrong_command] + + # Send the word to the Hunspell process + if {!${::MICROSOFT_WINDOWS}} { + ::X::secure_send ${::Editor::spellchecker_RAP_ID} print_line "{$word}" + } else { + dde eval ${::Editor::spellchecker_RAP_ID} print_line "{$word}" + } +} + +## Refresh list of available spell checker dictionaries (refresh in GUI) + # @return void +proc refresh_available_dictionaries {} { + # Abort if the Hunspell program is not available + if {!${::PROGRAM_AVAILABLE(hunspell)}} { + return + } + # This function was not yet ported to MS Windows + if {$::MICROSOFT_WINDOWS} { + return + } + + # Set widget descriptor for the dictionary selection menu + set m {.spell_checker_conf_menu} + + # Destroy the dictionary selection menu if it exists + if {[winfo exists $m]} { + destroy $m + } + + # Create new dictionary selection menu + menu $m ;# Main part + menu $m.by_country ;# Cascade "Set dictionary by country" + menu $m.by_language ;# Cascade "Set dictionary by language" + + # Define contents of the newly created menu + $m add command \ + -label [mc "Refresh list of dictionaries"] \ + -image ::ICONS::16::reload \ + -compound left \ + -command { + ::Editor::refresh_available_dictionaries + ::Editor::adjust_spell_checker_config_button + } + $m add command \ + -label [mc "Turn off spell checking"] \ + -image ::ICONS::16::exit \ + -compound left \ + -command {::Editor::switch_SC_dictionary {}} + $m add separator + $m add cascade \ + -label [mc "Set dictionary by language"] \ + -menu $m.by_language + $m add cascade \ + -label [mc "Set dictionary by country"] \ + -menu $m.by_country + + ## Get list of available Hunspell dictionaries + set ::Editor::available_dictionaries [list] + # Start watchdog timer for the Hunspell process + set spellchecker_start_timer [after 10000 { + catch { + close ${::Editor::hunspell_process} + } + }] + if {[catch { + # Run Hunspell in a mode in which it prints available dictionaries + if {!${::MICROSOFT_WINDOWS}} { + set hunspell_process [open {| /bin/sh -c "hunspell -D 2>&1 | awk '{print(\$0)} /^LOADED DICTIONARY/ {exit 0}' || exit 1"} "r"] + } else { + puts stderr "Sorry, this feature is not implemented on MS Windows yet." + error "Not available on Windows." + } + + }]} then { + # Error condition + puts stderr "Unable to start the Hunspell process" + + } else { + # Bool: Accept this line of output from the process + set accept_flag 0 + + # Read list of dictionaries (file names along with pats) + while {![eof $hunspell_process]} { + # Read line from the process + set line [gets $hunspell_process] + + # Ignore everything besides section ``AVAILABLE DICTIONARIES'' + if {![string first {AVAILABLE DICTIONARIES} $line]} { + set accept_flag 1 + continue + } elseif {![string first {LOADED DICTIONARY:} $line]} { + break + } elseif {!$accept_flag} { + continue + } + + # Determinate language code and country code and append it to the + #+ list of available dictionaries + set line [lindex [split $line [file separator]] end] + set line [split $line {_}] + if {[lindex $line 0] == {hyph}} { + continue +# set line [lreplace $line 0 0] + } + if {![string length [lindex $line 0]] || ![string length [lindex $line 1]]} { + continue + } + if {![string is alpha [lindex $line 0]] || ![string is alpha [lindex $line 1]]} { + continue + } + set dictionary [string tolower [lindex $line 0]]_[string toupper [lindex $line 1]] + if {[lsearch -ascii -exact ${::Editor::available_dictionaries} $dictionary] == -1} { + lappend ::Editor::available_dictionaries $dictionary + } + } + } + + # Cancel the watchdog timer + catch { + after cancel $spellchecker_start_timer + } + + # If there are no dictionaries available to use then abort right away + if {![llength ${::Editor::available_dictionaries}]} { + return + } + + + ## Enrich the gained list with some additional information + # + # Format of the resulting list: + # { + # {Language_code Country_code Country_name Language_name Flag_icon} + # ... + # } + set available_dictionaries_complex [list] + foreach dictionary ${::Editor::available_dictionaries} { + # Determinate language code and country code + set dictionary [split $dictionary {_}] ;# List: Language and country codes, e.g. {en GB} + set language_code [lindex $dictionary 0] ;# String: Language code, e.g. "en" + set country_code [lindex $dictionary 1] ;# String: County code, e.g. "GB" + set country_and_flag {} ;# List: Country name and flag icon name, e.g. {"United Kingdom" United_Kingdom} + set country_name {} ;# String: Country name, e.g. "United Kingdom" + set flag_icon {} ;# String: Flag icon name, e.g. "United_Kingdom" + set language_name {} ;# String: Language name, e.g. "English" + + # Determinate country name and flag file name + set idx [lsearch -ascii -exact -index 1 ${::Editor::COUNTRY_CODES_AND_FLAGS} $country_code] + if {$idx != -1} { + set country_and_flag [lindex ${::Editor::COUNTRY_CODES_AND_FLAGS} $idx] + set country_name [lindex $country_and_flag 0] + set flag_icon [lindex $country_and_flag 2] + } else { + set country_name $country_code + } + if {$flag_icon == {}} { + set flag_icon {empty} + } + + # Determinate language name + set idx [lsearch -ascii -exact -index 1 ${::Editor::LANGUAGE_CODES_AND_NAMES} $language_code] + if {$idx != -1} { + set language_name [lindex ${::Editor::LANGUAGE_CODES_AND_NAMES} [list $idx 0]] + } else { + set language_name $language_code + } + + if {$country_name == {}} { + set country_name {???} + } + if {$language_name == {}} { + set language_name {???} + } + + # Append item to the resulting list + lappend available_dictionaries_complex [list $language_code $country_code [mc $country_name] [mc $language_name] $flag_icon] + } + + # Generate content of the "Set by country" menu + set local_menu {} + set capital_leter {} + set last_capital_leter {} + foreach dictionary [lsort -dictionary -index 2 [lsort -dictionary -index 3 $available_dictionaries_complex]] { + # Gain some facts about the dictionary file + set language_code [lindex $dictionary 0] + set country_code [lindex $dictionary 1] + set country_name [lindex $dictionary 2] + set language_name [lindex $dictionary 3] + set flag_icon [lindex $dictionary 4] + + # Create sub-menu if necessary + set capital_leter [string toupper [string index $country_name 0]] + + if {$capital_leter != $last_capital_leter} { + set last_capital_leter $capital_leter + set local_menu [menu $m.by_country.m_[string tolower $capital_leter]] + $m.by_country add cascade -label "$capital_leter ..." -menu $local_menu + } + + # Create the menu item + $local_menu add command \ + -command "::Editor::switch_SC_dictionary {${language_code}_${country_code}}" \ + -label "$country_name ($language_name)" \ + -image ::ICONS::flag::$flag_icon \ + -compound left + } + + # Generate content of the "Set by language" menu + set local_menu {} + set capital_leter {} + set last_capital_leter {} + foreach dictionary [lsort -dictionary -index 3 [lsort -dictionary -index 2 $available_dictionaries_complex]] { + # Gain some facts about the dictionary file + set language_code [lindex $dictionary 0] + set country_code [lindex $dictionary 1] + set country_name [lindex $dictionary 2] + set language_name [lindex $dictionary 3] + set flag_icon [lindex $dictionary 4] + + # Create sub-menu if necessary + set capital_leter [string toupper [string index $language_name 0]] + if {$capital_leter != $last_capital_leter} { + set last_capital_leter $capital_leter + set local_menu [menu $m.by_language.m_[string tolower $capital_leter]] + $m.by_language add cascade -label "$capital_leter ..." -menu $local_menu + } + + # Create the menu item + $local_menu add command \ + -command "::Editor::switch_SC_dictionary {${language_code}_${country_code}}" \ + -label "$language_name ($country_name)" \ + -image ::ICONS::flag::$flag_icon \ + -compound left + } +} + +## Switch current dictionary + # @parm String dictionary - Dictionary name like: ``en_GB'' or ``en_AU'' + # @return void +proc switch_SC_dictionary {dictionary} { + # Abort if the Hunspell program is not available + if {!${::PROGRAM_AVAILABLE(hunspell)}} { + return + } + + # This function was not yet ported to MS Windows + if {$::MICROSOFT_WINDOWS} { + return + } + + # Chech whether the requested dictionary is available + if {[lsearch -ascii -exact ${::Editor::available_dictionaries} $dictionary] == -1} { + set dictionary {} + } + + # Empty dictionary name means disable the feature + if {[string length $dictionary]} { + set ::Editor::spellchecker_enabled 1 + } else { + set ::Editor::spellchecker_enabled 0 + } + + # Adjust configuration button + .statusbarSB configure \ + -image ::ICONS::16::player_time \ + -text "<>" + + # Clear spell checker's tags in all text editors + foreach project ${::X::openedProjects} { + foreach editor [$project cget -editors] { + $editor spellchecker_clear_tags + } + } + + # Switch the dictionary + set ::Editor::spellchecker_dictionary $dictionary + restart_spellchecker_process + adjust_spell_checker_config_button + + # Refresh all editors + foreach project ${::X::openedProjects} { + foreach editor [$project cget -editors] { + $editor parseAll + } + } +} + +## Adjust spell checker configuration button to current spell checker configuration + # @return void +proc adjust_spell_checker_config_button {} { + # Abort if the Hunspell program is not available + if {!${::PROGRAM_AVAILABLE(hunspell)}} { + return + } + # This function was not yet ported to MS Windows + if {$::MICROSOFT_WINDOWS} { + return + } + + # Spell checker configuration menu + set m {.spell_checker_conf_menu} + + ## Spell checker is DISABLED + if {!$::Editor::spellchecker_enabled} { + $m entryconfigure [mc "Turn off spell checking"] -state disabled + .statusbarSB configure \ + -image ::ICONS::flag::empty \ + -text "none" + + ## Spell checker is ENABLED + } else { + $m entryconfigure [mc "Turn off spell checking"] -state normal + + set c_l [split ${::Editor::spellchecker_dictionary} {_}] + set idx [lsearch -ascii -exact -index 1 ${::Editor::COUNTRY_CODES_AND_FLAGS} [lindex $c_l 1]] + if {$idx != -1} { + set flag_icon [lindex ${::Editor::COUNTRY_CODES_AND_FLAGS} [list $idx 2]] + } else { + set flag_icon {empty} + } + + .statusbarSB configure \ + -image ::ICONS::flag::$flag_icon \ + -text [lindex $c_l 0] + } +} + +## By calling this method we mark the target line as something which is a subject for a change + # Purpose is to handle insertions of single characters and deletions of single characters + # @note This method inhibits method spellcheck_check_all until spellcheck_change_detected_post is called + # @see spellcheck_change_detected_post + # @parm Int line_number = {} - Number of the target line, {} means the current line + # @return void +private method spellcheck_change_detected_pre {{line_number {}}} { + # This function was not yet ported to MS Windows + if {$::MICROSOFT_WINDOWS} { + return + } + + # Abort conditions + if {!$spellchecker_enabled || !${::PROGRAM_AVAILABLE(hunspell)}} { + return + } + + # Inhibit method spellcheck_check_all until spellcheck_change_detected_post is called + set spellcheck_lock 1 + + # Adjust parameters + if {$line_number == {}} { + set line_number [expr {int([$editor index insert])}] + } + + # Store the target line + set spellcheck_line_number $line_number + set spellcheck_line_pre [$editor get [list $line_number.0 linestart] [list $line_number.0 lineend]] +} + +## By calling this method we finalize the process started by calling to method spellcheck_change_detected_pre + # Purpose is to handle insertions of single characters and deletions of single characters + # @see spellcheck_change_detected_pre + # @parm Int line_number = {} - Number of the target line, {} means the current line + # @return void +private method spellcheck_change_detected_post {{line_number {}}} { + # This function was not yet ported to MS Windows + if {$::MICROSOFT_WINDOWS} { + return + } + + # Abort conditions + if {!$spellchecker_enabled || !${::PROGRAM_AVAILABLE(hunspell)}} { + return + } + + # cancel the inhibition of method spellcheck_check_all + set spellcheck_lock 0 + + # Adjust parameters + if {$line_number == {}} { + set line_number [expr {int([$editor index insert])}] + } + + # Determinate ranges of text indexes delimiting strings to check for spelling + if {$prog_language == -1} { + set target_ranges [list [list $line_number.0 [$editor index [list $line_number.0 lineend]]]] + } else { + set target_ranges [list] + set range [list $line_number.0 $line_number.0] + while {1} { + set range [concat \ + [$editor tag nextrange tag_comment [lindex $range 1] [list $line_number.0 lineend]] \ + [$editor tag nextrange tag_c_comment [lindex $range 1] [list $line_number.0 lineend]] \ + [$editor tag nextrange tag_c_dox_comment [lindex $range 1] [list $line_number.0 lineend]] \ + ] + + if {![llength $range]} { + break + } + + lappend target_ranges $range + } + } + + # Gain entire line from the editor + set line [$editor get [list $line_number.0 linestart] [list $line_number.0 lineend]] + + if {[string length $line] > [string length $spellcheck_line_pre]} { + set new_longer_that_org 1 + } else { + set new_longer_that_org 0 + } + + # Compare the line to its previous content and check changed word(s) + set fixed_shift 0 ;# Total pre string shift from all cycles + set force_check 0 ;# Enforce spell check of the next word regardless the comparison + foreach range $target_ranges { + # Determinate start and end column + scan [lindex $range 0] {%d.%d} _ start + scan [lindex $range 1] {%d.%d} _ end + + set word {} ;# String: Word to check + set char {} ;# Char: Character gained from the source line + set idx_pre $start ;# Int: Index in $spellcheck_line_pre + set word_len 0 ;# Int: Length of the word + set skip_word 0 ;# Bool: Flag skip this one word + set change_detected 0 ;# Bool: Flag the word was changed + set char_next [string index $line $start] ;# Char: Same as char but maybe a little ahead + + # Take into accound shift from previous cycles + incr idx_pre $fixed_shift + + for {set idx $start} {$idx < $end} {incr idx; incr idx_pre} { + set char $char_next + set char_next [string index $line [expr {$idx + 1}]] + set char_pre_next [string index $spellcheck_line_pre [expr {$idx_pre + 1}]] + set char_pre [string index $spellcheck_line_pre $idx_pre] + + if {[string is alnum $char]} { + # If the word contains one or more digits, skip it, digits in a word + #+ would cause Hunspell to behave in a way that we don't want here + if {[string is digit $char]} { + set skip_word 1 + } + + # Lines are different + if {$char_pre != $char} { + set change_detected 1 + + # Character deleted -- shift the pre string >> 1 + if {$char_pre_next == $char && !$new_longer_that_org} { + incr idx_pre + incr fixed_shift + # Character inserted -- shift the pre string << 1 + } elseif {$char_pre == $char_next && $new_longer_that_org} { + incr idx_pre -1 + incr fixed_shift -1 + } + + # Character appended at the end of the word -- shift the pre string << 1, + #+ and check for the next word unconditionally + } elseif { + [string is alnum $char_pre_next] + && + ![string is alnum $char_next] + && + $char_pre_next != $char_next + } then { + incr idx_pre -1 + incr fixed_shift -1 + set change_detected 1 + set force_check 1 + } + + # Append the character to the word + append word $char + incr word_len + + # This is not the last character in the line + if {$idx < ($end - 1)} { + continue + # This IS the last character in the line + } else { + incr idx + } + } + + # Skip empty words + if {!$word_len} { + continue + } + + # Send the word to the spell checker + if {$change_detected && !$skip_word} { + set change_detected 0 + $editor tag remove tag_wrong_spelling $line_number.$idx-${word_len}c-1c $line_number.$idx + + spellchecker_check_word $word \ + [list $editor tag add tag_wrong_spelling $line_number.$idx-${word_len}c $line_number.$idx] \ + [list $editor tag remove tag_wrong_spelling $line_number.$idx-${word_len}c $line_number.$idx] + } + + # Enforce spell check of the next word regardless the comparison + if {$force_check} { + set force_check 0 + set change_detected 1 + } + + # Reset + set word {} + set word_len 0 + set skip_word 0 + } + } +} + +## Check spelling on the specified line + # + # This method will not perform the task if $spellcheck_line_number is equal to + # the given source line, unless the force parameter is set to true. + # @note + # Spell checking is performed only for comments unless the programming language + # is not specified + # @parm Int line_number - Number of line to check + # @parm Int force = 0 - 1: Force the method to perform the spell check; 2: Force even over $spellcheck_lock + # @return void +public method spellcheck_check_all {line_number {force 0}} { + # This function was not yet ported to MS Windows + if {$::MICROSOFT_WINDOWS} { + return + } + + # Abort conditions + if {($force != 2 && $spellcheck_lock) || !$spellchecker_enabled || !${::PROGRAM_AVAILABLE(hunspell)}} { + return + } + if {!$force && ($spellcheck_line_number != $line_number)} { + return + } + set spellcheck_line_number {} + + # Remove bad spelling tags + $editor tag remove tag_wrong_spelling $line_number.0 [list $line_number.0 lineend] + + # Determinate ranges of text indexes delimiting strings to check for spelling + if {$prog_language == -1} { + set target_ranges [list [list $line_number.0 [$editor index [list $line_number.0 lineend]]]] + } else { + set target_ranges [list] + set range [list $line_number.0 $line_number.0] + while {1} { + set range [concat \ + [$editor tag nextrange tag_comment [lindex $range 1] [list $line_number.0 lineend]] \ + [$editor tag nextrange tag_c_comment [lindex $range 1] [list $line_number.0 lineend]] \ + [$editor tag nextrange tag_c_dox_comment [lindex $range 1] [list $line_number.0 lineend]] \ + ] + + if {![llength $range]} { + break + } + + lappend target_ranges $range + } + } + + # Gain entire line from the editor + set line [$editor get $line_number.0 [list $line_number.0 lineend]] + + # Check spelling for the given ranges + foreach range $target_ranges { + # Determinate start and end column + scan [lindex $range 0] {%d.%d} _ start + scan [lindex $range 1] {%d.%d} _ end + + set word {} ;# String: Word to check + set char {} ;# Char: Character gained from the source line + set word_len 0 ;# Int: Length of the word + set skip_word 0 ;# Bool: Flag skip this one word + + # Iterate over characters in the source line + for {set idx $start} {$idx < $end} {incr idx} { + set char [string index $line $idx] + + if {[string is alnum $char]} { + # If the word contains one or more digits, skip it digits in a word + #+ would cause Hunspell to behave in a way that we don't want here + if {[string is digit $char]} { + set skip_word 1 + } + + # Append the character to the word + append word $char + incr word_len + + # This is not the last character in the line + if {$idx < ($end - 1)} { + continue + # This IS the last character in the line + } else { + incr idx + } + } + + # Skip empty words + if {!$word_len} { + continue + } + + # Send the word to the spell checker + if {!$skip_word} { + spellchecker_check_word \ + $word \ + [list $editor tag add tag_wrong_spelling $line_number.$idx-${word_len}c $line_number.$idx] + } + + # Reset + set word {} + set word_len 0 + set skip_word 0 + } + } +} + +## Clear all tags marking the misspelled words + # @return void +public method spellchecker_clear_tags {} { + $editor tag remove tag_wrong_spelling 0.0 end +} + +# >>> File inclusion guard +} +# <<< File inclusion guard |