aboutsummaryrefslogtreecommitdiff
path: root/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
blob: 506179297ad059fb0ae3725d0d3d00f23fd12083 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
" Debugger plugin using gdb.
"
" WORK IN PROGRESS - much doesn't work yet
"
" Open two visible terminal windows:
" 1. run a pty, as with ":term NONE"
" 2. run gdb, passing the pty
" The current window is used to view source code and follows gdb.
"
" A third terminal window is hidden, it is used for communication with gdb.
"
" The communication with gdb uses GDB/MI.  See:
" https://sourceware.org/gdb/current/onlinedocs/gdb/GDB_002fMI.html
"
" Author: Bram Moolenaar
" Copyright: Vim license applies, see ":help license"

" In case this gets loaded twice.
if exists(':Termdebug')
  finish
endif

" The command that starts debugging, e.g. ":Termdebug vim".
" To end type "quit" in the gdb window.
command -nargs=* -complete=file Termdebug call s:StartDebug(<q-args>)

" Name of the gdb command, defaults to "gdb".
if !exists('debugger')
  let debugger = 'gdb'
endif

" Sign used to highlight the line where the program has stopped.
sign define debugPC linehl=debugPC
if &background == 'light'
  hi debugPC term=reverse ctermbg=lightblue guibg=lightblue
else
  hi debugPC term=reverse ctermbg=darkblue guibg=darkblue
endif
let s:pc_id = 12

func s:StartDebug(cmd)
  let s:startwin = win_getid(winnr())
  let s:startsigncolumn = &signcolumn

  " Open a terminal window without a job, to run the debugged program
  let s:ptybuf = term_start('NONE', {
	\ 'term_name': 'gdb program',
	\ })
  if s:ptybuf == 0
    echoerr 'Failed to open the program terminal window'
    return
  endif
  let pty = job_info(term_getjob(s:ptybuf))['tty_out']

  " Create a hidden terminal window to communicate with gdb
  let s:commbuf = term_start('NONE', {
	\ 'term_name': 'gdb communication',
	\ 'out_cb': function('s:CommOutput'),
	\ 'hidden': 1,
	\ })
  if s:commbuf == 0
    echoerr 'Failed to open the communication terminal window'
    exe 'bwipe! ' . s:ptybuf
    return
  endif
  let commpty = job_info(term_getjob(s:commbuf))['tty_out']

  " Open a terminal window to run the debugger.
  let cmd = [g:debugger, '-tty', pty, a:cmd]
  echomsg 'executing "' . join(cmd) . '"'
  let gdbbuf = term_start(cmd, {
	\ 'exit_cb': function('s:EndDebug'),
	\ 'term_finish': 'close',
	\ })
  if gdbbuf == 0
    echoerr 'Failed to open the gdb terminal window'
    exe 'bwipe! ' . s:ptybuf
    exe 'bwipe! ' . s:commbuf
    return
  endif

  " Connect gdb to the communication pty, using the GDB/MI interface
  call term_sendkeys(gdbbuf, 'new-ui mi ' . commpty . "\r")
endfunc

func s:EndDebug(job, status)
  exe 'bwipe! ' . s:ptybuf
  exe 'bwipe! ' . s:commbuf
  call setwinvar(s:startwin, '&signcolumn', s:startsigncolumn)
endfunc

" Handle a message received from gdb on the GDB/MI interface.
func s:CommOutput(chan, msg)
  let msgs = split(a:msg, "\r")

  for msg in msgs
    " remove prefixed NL
    if msg[0] == "\n"
      let msg = msg[1:]
    endif
    if msg != ''
      if msg =~ '^\*\(stopped\|running\)'
	let wid = win_getid(winnr())

	if win_gotoid(s:startwin)
	  if msg =~ '^\*stopped'
	    " TODO: proper parsing
	    let fname = substitute(msg, '.*fullname="\([^"]*\)".*', '\1', '')
	    let lnum = substitute(msg, '.*line="\([^"]*\)".*', '\1', '')
	    if lnum =~ '^[0-9]*$'
	      if expand('%:h') != fname
		if &modified
		  " TODO: find existing window
		  exe 'split ' . fnameescape(fname)
		  let s:startwin = win_getid(winnr())
		else
		  exe 'edit ' . fnameescape(fname)
		endif
	      endif
	      exe lnum
	      exe 'sign place ' . s:pc_id . ' line=' . lnum . ' name=debugPC file=' . fnameescape(fname)
	      setlocal signcolumn=yes
	    endif
	  else
	    exe 'sign unplace ' . s:pc_id
	  endif

	  call win_gotoid(wid)
	endif
      endif
    endif
  endfor
endfunc