text: from gosubl import gs
  from gosubl import gsq
  from gosubl import gsshell
  from gosubl import mg9
  import os
  import re
  import sublime
  import sublime_plugin
  import threading
  import time
  
  DOMAIN = 'GsLint'
  CL_DOMAIN = 'GsCompLint'
  
  class FileRef(object):
  	def __init__(self, view):
  		self.view = view
  		self.src = ''
  		self.tm = 0.0
  		self.state = 0
  		self.reports = {}
  
  class Report(object):
  	def __init__(self, row, col, msg):
  		self.row = row
  		self.col = col
  		self.msg = msg
  
  class GsLintThread(threading.Thread):
  	def __init__(self):
  		threading.Thread.__init__(self)
  		self.daemon = True
  		self.sem = threading.Semaphore()
  		self.s = set()
  		self.q = gs.queue.Queue()
  
  	def putq(self, fn):
  		with self.sem:
  			if fn in self.s:
  				return False
  			self.s.add(fn)
  			self.q.put(fn)
  			return True
  
  	def popq(self):
  		fn = self.q.get()
  		with self.sem:
  			self.s.discard(fn)
  		return fn
  
  	def run(self):
  		while True:
  			fn = self.popq()
  			fr = ref(fn, False)
  			if fr:
  				reports = {}
  				res, _ = mg9.bcall('lint', {
  					'dir': (os.path.dirname(fn) if fn else ''),
  					'fn': fn,
  					'src': fr.src,
  					'filter': gs.setting('lint_filter', []),
  				})
  				res = gs.dval(res, {})
  				for r in gs.dval(res.get('reports'), []):
  					if fn and fn != '<stdin>' and r.get('Fn') != fn:
  						continue
  
  					kind = r.get('Kind', '')
  					row = r.get('Row', 0)
  					col = r.get('Col', 0)
  					msg = r.get('Message', '')
  					msg = '%s: %s' % (kind, msg)
  					if row >= 0 and msg:
  						reports[row] = Report(row, col, msg)
  
  				fr = ref(fn, False)
  				if fr:
  					with sem:
  						fr.state = 1
  						fr.reports = reports
  						file_refs[fn] = fr
  
  def highlight(fr):
  	sel = gs.sel(fr.view).begin()
  	row, _ = fr.view.rowcol(sel)
  
  	if fr.state == 1:
  		fr.state = 0
  		cleanup(fr.view)
  
  		regions = []
  		regions0 = []
  		domain0 = DOMAIN+'-zero'
  		for r in fr.reports.values():
  			line = fr.view.line(fr.view.text_point(r.row, 0))
  			pos = line.begin() + r.col
  			if pos >= line.end():
  				pos = line.end()
  			if pos == line.begin():
  				regions0.append(sublime.Region(pos, pos))
  			else:
  				regions.append(sublime.Region(pos, pos))
  
  		if regions:
  			fr.view.add_regions(DOMAIN, regions, 'comment', 'dot', sublime.DRAW_EMPTY_AS_OVERWRITE)
  		else:
  			fr.view.erase_regions(DOMAIN)
  
  		if regions0:
  			fr.view.add_regions(domain0, regions0, 'comment', 'dot', sublime.HIDDEN)
  		else:
  			fr.view.erase_regions(domain0)
  
  	msg = ''
  	reps = fr.reports.copy()
  	l = len(reps)
  	if l > 0:
  		msg = '%s (%d)' % (DOMAIN, l)
  		r = reps.get(row)
  		if r and r.msg:
  			msg = '%s: %s' % (msg, r.msg)
  
  	if fr.state != 0:
  		msg = u'\u231B %s' % msg
  
  	fr.view.set_status(DOMAIN, msg)
  
  def cleanup(view):
  	view.set_status(DOMAIN, '')
  	view.erase_regions(DOMAIN)
  	view.erase_regions(DOMAIN+'-zero')
  
  def watch():
  	global file_refs
  	global th
  
  	view = gs.active_valid_go_view()
  
  	if view is not None and (view.file_name() and gs.setting('comp_lint_enabled') is True):
  		fn = view.file_name()
  		fr = ref(fn)
  		with sem:
  			if fr:
  				fr.view = view
  				highlight(fr)
  		sublime.set_timeout(watch, 500)
  		return
  
  
  	if gs.setting('gslint_enabled') is not True:
  		if view:
  			with sem:
  				for fn in file_refs:
  					fr = file_refs[fn]
  					cleanup(fr.view)
  				file_refs = {}
  		sublime.set_timeout(watch, 2000)
  		return
  
  	if view and not view.is_loading():
  		fn = view.file_name()
  		fr = ref(fn)
  		with sem:
  			if fr:
  				# always use the active view (e.g in split-panes)
  				fr.view = view
  				highlight(fr)
  			else:
  				fr = FileRef(view)
  
  			file_refs[fn] = fr
  			if fr.state == 0:
  				src = view.substr(sublime.Region(0, view.size()))
  				if src != fr.src:
  					fr.src = src
  					fr.tm = time.time()
  
  				if fr.tm > 0.0:
  					timeout = int(gs.setting('gslint_timeout', 500))
  					delta = int((time.time() - fr.tm) * 1000.0)
  					if delta >= timeout:
  						fr.tm = 0.0
  						fr.state = -1
  						if not th:
  							th = GsLintThread()
  							th.start()
  						th.putq(fn)
  
  	sublime.set_timeout(watch, 500)
  
  def ref(fn, validate=True):
  	with sem:
  		if validate:
  			for fn in list(file_refs.keys()):
  				fr = file_refs[fn]
  				if not fr.view.window() or fn != fr.view.file_name():
  					del file_refs[fn]
  		return file_refs.get(fn)
  
  def delref(fn):
  	with sem:
  		if fn in file_refs:
  			del file_refs[fn]
  
  
  def do_comp_lint(dirname, fn):
  	fr = ref(fn, False)
  	reports = {}
  	if not fr:
  		return
  
  	fn = gs.apath(fn, dirname)
  	bindir, _ = gs.temp_dir('bin')
  	local_env = {
  		'GOBIN': bindir,
  	}
  
  	pat = r'%s:(\d+)(?:[:](\d+))?\W+(.+)\s*' % re.escape(os.path.basename(fn))
  	pat = re.compile(pat, re.IGNORECASE)
  	for c in gs.setting('comp_lint_commands'):
  		try:
  			cmd = c.get('cmd')
  			if not cmd:
  				continue
  			cmd_domain = ' '.join(cmd)
  
  			shell = c.get('shell') is True
  			env = {} if c.get('global') is True else local_env
  			out, err, _ = gsshell.run(cmd=cmd, shell=shell, cwd=dirname, env=env)
  			if err:
  				gs.notice(DOMAIN, err)
  
  			out = out.replace('\r', '').replace('\n ', '\\n ').replace('\n\t', '\\n\t')
  			for m in pat.findall(out):
  				try:
  					row, col, msg = m
  					row = int(row)-1
  					col = int(col)-1 if col else 0
  					msg = msg.replace('\\n', '\n').strip()
  					if row >= 0 and msg:
  						msg = '%s: %s' % (cmd_domain, msg)
  						if reports.get(row):
  							reports[row].msg = '%s\n%s' % (reports[row].msg, msg)
  							reports[row].col = max(reports[row].col, col)
  						else:
  							reports[row] = Report(row, col, msg)
  				except:
  					pass
  		except:
  			gs.notice(DOMAIN, gs.traceback())
  
  	def cb():
  		fr.reports = reports
  		fr.state = 1
  		highlight(fr)
  	sublime.set_timeout(cb, 0)
  
  class GsCompLintCommand(sublime_plugin.TextCommand):
  	def run(self, edit):
  		if gs.setting('comp_lint_enabled') is not True:
  			return
  
  		fn = self.view.file_name()
  		fn = os.path.abspath(fn)
  		if fn:
  			dirname = gs.basedir_or_cwd(fn)
  			file_refs[fn] = FileRef(self.view)
  			gsq.dispatch(CL_DOMAIN, lambda: do_comp_lint(dirname, fn), '')
  
  try:
  	th
  except:
  	th = None
  	sem = threading.Semaphore()
  	file_refs = {}
  
  	watch()
  
  