#!/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
