v0.2.2 9100209

pass.go RawHistoryFolder

1// package pass implements the first and second pass to go from gir files to go files
2// the first pass collects basic type information
3// the second pass uses the basic type information to go over the gir files again and convert it to go files
4package pass
5
6import (
7	"encoding/xml"
8	"fmt"
9	"os"
10	"strings"
11	"text/template"
12
13	"github.com/bnema/puregotk/internal/gir/types"
14	"github.com/bnema/puregotk/internal/gir/util"
15)
16
17type Pass struct {
18	Parsed []types.Repository
19	Types  types.KindMap
20}
21
22// New creates a new pass struct by parsing gir files in the string slice
23// This pass object will then be used to go over these files multiple times up until we have the full info to convert it to go files
24func New(files []string) (*Pass, error) {
25	p := Pass{
26		Parsed: make([]types.Repository, len(files)),
27		Types:  make(types.KindMap),
28	}
29	for i, f := range files {
30		b, err := os.ReadFile(f)
31		if err != nil {
32			return nil, err
33		}
34		var r types.Repository
35		err = xml.Unmarshal(b, &r)
36		if err != nil {
37			return nil, err
38		}
39		p.Parsed[i] = r
40	}
41	return &p, nil
42}
43
44func (p *Pass) collectTypes(r types.Repository) {
45	ns := r.Namespaces[0]
46	for _, cls := range ns.Classes {
47		p.Types.Add(ns.Name, cls.Name, types.ClassesType, cls)
48	}
49	for _, rec := range ns.Records {
50		p.Types.Add(ns.Name, rec.Name, types.RecordsType, rec)
51	}
52	for _, en := range ns.Enums {
53		// TODO: This probably shouldn't be aliastype, but we should make dedicated types
54		p.Types.Add(ns.Name, en.Name, types.AliasType, en)
55	}
56	for _, cb := range ns.Callbacks {
57		p.Types.Add(ns.Name, cb.Name, types.CallbackType, cb)
58	}
59	for _, b := range ns.Bitfields {
60		// TODO: This probably shouldn't be aliastype, but we should make dedicated types
61		p.Types.Add(ns.Name, b.Name, types.AliasType, b)
62	}
63	for _, inter := range ns.Interfaces {
64		p.Types.Add(ns.Name, inter.Name, types.InterfacesType, inter)
65	}
66	for _, alias := range ns.Aliases {
67		// Check what the alias points to and use the same type
68		aliasTarget := alias.Type.Name
69		if aliasTarget != "" {
70			targetKind := p.Types.Kind(ns.Name, aliasTarget)
71			if targetKind != types.UnknownType {
72				p.Types.Add(ns.Name, alias.Name, targetKind, alias)
73			} else {
74				// If we don't know the target type yet, default to alias type
75				p.Types.Add(ns.Name, alias.Name, types.AliasType, alias)
76			}
77		} else {
78			p.Types.Add(ns.Name, alias.Name, types.AliasType, alias)
79		}
80	}
81}
82
83// First does a "first pass" meaning it collects basic type information for all the repositories
84func (p *Pass) First() {
85	for _, r := range p.Parsed {
86		p.collectTypes(r)
87	}
88}
89
90func (p *Pass) writeGo(r types.Repository, gotemp *template.Template, dir string) {
91	ns := r.Namespaces[0]
92
93	aliases := make(map[string][]types.AliasTemplate)
94	enums := make(map[string][]types.EnumTemplate)
95	var files []string
96	for _, el := range ns.Bitfields {
97		temp := el.Template(ns.Name)
98		fn := el.FilenameSafe()
99		files = append(files, fn)
100		enums[fn] = append(enums[fn], temp)
101	}
102
103	for _, el := range ns.Enums {
104		temp := el.Template(ns.Name)
105		fn := el.FilenameSafe()
106		files = append(files, fn)
107		enums[fn] = append(enums[fn], temp)
108	}
109
110	constants := make(map[string][]types.ConstantTemplate)
111	for _, con := range ns.Constants {
112		fn := con.FilenameSafe()
113		files = append(files, fn)
114		constants[fn] = append(constants[fn], con.Template(ns.Name, p.Types))
115	}
116
117	callbackDocs := make(map[string]string)
118	for _, cb := range ns.Callbacks {
119		callbackDocs[cb.Name] = cb.Doc.StringSafe()
120	}
121
122	records := make(map[string][]types.RecordTemplate)
123	recordLookup := make(map[string]bool)
124	for _, rec := range ns.Records {
125		name := util.SnakeToCamel(rec.Name)
126		constructors := make([]types.FuncTemplate, len(rec.Constructors))
127		receivers := make([]types.FuncTemplate, 0, len(rec.Methods))
128		fields := make([]types.RecordField, 0, len(rec.Fields))
129		callbackAccessors := make([]types.CallbackAccessor, 0)
130		fn := rec.FilenameSafe()
131		files = append(files, fn)
132		for i, c := range rec.Constructors {
133			constructors[i] = types.FuncTemplate{
134				Name:  util.ConstructorName(c.Name, rec.Name),
135				CName: c.CIdentifier,
136				Doc:   c.Doc.StringSafe(),
137				Args:  c.Parameters.Template(ns.Name, "", p.Types, c.Throws, types.ArgsFromGoToC),
138				Ret:   c.ReturnValue.Template(ns.Name, "", p.Types, c.Throws),
139			}
140		}
141		for _, f := range rec.Fields {
142			var _type string
143			var fieldName string
144
145			// Check if this field is a callback
146			if f.Callback != nil {
147				_type = "uintptr"
148				fieldName = "x" + util.SnakeToCamel(f.Name) // Prefix callback pointer fields with `x` to make them private
149
150				callbackName := util.SnakeToCamel(f.Name)
151				args := f.Callback.Parameters.Template(ns.Name, "", p.Types, f.Callback.Throws, types.ArgsFromCToGo)
152				ret := f.Callback.ReturnValue.Template(ns.Name, "", p.Types, f.Callback.Throws)
153
154				apiTypes := args.API.Types
155
156				var doc string
157				if f.Callback.Doc != nil && f.Callback.Doc.String != "" {
158					doc = f.Callback.Doc.StringSafe()
159				} else {
160					baseClassName := strings.TrimSuffix(rec.Name, "Class")
161					callbackName := baseClassName + util.SnakeToCamel(f.Name) + "Func"
162
163					if callbackDoc, exists := callbackDocs[callbackName]; exists && callbackDoc != "" {
164						doc = callbackDoc
165					} else {
166						doc = f.Doc.StringSafe()
167					}
168				}
169
170				callbackAccessors = append(callbackAccessors, types.CallbackAccessor{
171					Name:         callbackName,
172					CName:        f.Name,
173					Doc:          doc,
174					CallbackType: "func(" + strings.Join(apiTypes, ", ") + ") " + ret.Value,
175					Args:         args,
176					Ret:          ret,
177				})
178			} else {
179				_type = f.Translate(ns.Name, p.Types)
180				if _type == "" {
181					continue
182				}
183				// HACK: Handle the specific case where a gint is converted to an int
184				// But for structs this needs to be an int32 as purego just gets the pointer to the struct
185				// Instead of converting each field separately
186				if f.AnyType.Type != nil && f.AnyType.Type.CType == "gint" {
187					_type = "int32"
188				}
189
190				// HACK: in structs the strings should be uintptr as we convert it ourselves
191				if _type == "string" {
192					_type = "uintptr"
193				}
194
195				// HACK: Special handling for parent_class field - it should be embedded as a full struct
196				// to match C's memory layout, not converted to uintptr
197				// See https://docs.gtk.org/gobject/tutorial.html
198				if f.Name == "parent_class" && f.AnyType.Type != nil {
199					// Check if this is a Record type with no pointers (embedded struct)
200					typeName := util.NormalizeNamespace(ns.Name, f.AnyType.Type.Name, true)
201					kind := p.Types.Kind(ns.Name, typeName)
202					if kind == types.RecordsType && !strings.Contains(f.AnyType.Type.CType, "*") {
203						// Use the full struct type for embedding
204						_type = typeName
205					}
206				}
207
208				fieldName = util.SnakeToCamel(f.Name)
209			}
210
211			fields = append(fields, types.RecordField{
212				Name: fieldName,
213				Type: _type,
214			})
215		}
216		for _, f := range rec.Methods {
217			name := util.SnakeToCamel(f.Name)
218			if name == "" {
219				name = util.SnakeToCamel(f.CIdentifier)
220			}
221			for _, f := range fields {
222				if f.Name == name {
223					name = name + "Fn"
224					break
225				}
226			}
227			receivers = append(receivers, types.FuncTemplate{
228				Doc:   f.Doc.StringSafe(),
229				Name:  name,
230				CName: f.CIdentifier,
231				Args:  f.Parameters.Template(ns.Name, "", p.Types, f.Throws, types.ArgsFromGoToC),
232				Ret:   f.ReturnValue.Template(ns.Name, "", p.Types, f.Throws),
233			})
234		}
235		records[fn] = append(records[fn], types.RecordTemplate{
236			Name:              name,
237			Doc:               rec.Doc.StringSafe(),
238			Constructors:      constructors,
239			Receivers:         receivers,
240			Fields:            fields,
241			CallbackAccessors: callbackAccessors,
242			TypeGetter:        rec.GLibGetType,
243		})
244		recordLookup[name] = true
245	}
246
247	callbacks := make(map[string][]types.CallbackTemplate)
248	// set every callback equal to uintptr as well
249	for _, cb := range ns.Callbacks {
250		fn := cb.FilenameSafe()
251		files = append(files, fn)
252		cbT := types.CallbackTemplate{
253			Doc:  cb.Doc.StringSafe(),
254			Name: cb.Name,
255			Args: cb.Parameters.Template(ns.Name, "", p.Types, cb.Throws, types.ArgsFromCToGo),
256			Ret:  cb.ReturnValue.Template(ns.Name, "", p.Types, cb.Throws),
257		}
258		callbacks[fn] = append(callbacks[fn], cbT)
259	}
260
261	interfaces := make(map[string][]types.InterfaceTemplate)
262	for _, inter := range ns.Interfaces {
263		fn := inter.FilenameSafe()
264		files = append(files, fn)
265		interfaces[fn] = append(interfaces[fn], types.ConvertInterface(ns.Name, "", inter, nil, p.Types))
266	}
267
268	for _, union := range ns.Unions {
269		fn := union.FilenameSafe()
270		files = append(files, fn)
271		name := util.SnakeToCamel(union.Name)
272		interT := types.AliasTemplate{
273			Doc:  union.Doc.StringSafe(),
274			Name: name,
275			// structs are not yet supported in CGO
276			Value: "uintptr",
277		}
278		aliases[fn] = append(aliases[fn], interT)
279	}
280
281	for _, alias := range ns.Aliases {
282		fn := alias.FilenameSafe()
283		files = append(files, fn)
284		typeName := alias.Template(ns.Name, p.Types)
285		if typeName == "" {
286			typeName = "uintptr"
287		}
288		name := util.SnakeToCamel(alias.Name)
289		aliasT := types.AliasTemplate{
290			Doc:  alias.Doc.StringSafe(),
291			Name: name,
292			// structs are not yet supported in CGO
293			Value: typeName,
294		}
295		aliases[fn] = append(aliases[fn], aliasT)
296	}
297
298	functions := make(map[string][]types.FuncTemplate)
299	for _, f := range ns.Functions {
300		name := util.SnakeToCamel(f.Name)
301		if p.Types.Kind(ns.Name, name) != types.UnknownType {
302			name = "New" + name
303		}
304		fn := f.FilenameSafe()
305		files = append(files, fn)
306		functions[fn] = append(functions[fn], types.FuncTemplate{
307			Name:  name,
308			CName: f.CIdentifier,
309			Doc:   f.Doc.StringSafe(),
310			Args:  f.Parameters.Template(ns.Name, "", p.Types, f.Throws, types.ArgsFromGoToC),
311			Ret:   f.ReturnValue.Template(ns.Name, "", p.Types, f.Throws),
312		})
313	}
314
315	classes := make(map[string][]types.ClassTemplate)
316	for _, cls := range ns.Classes {
317		implemented := make(map[string]bool)
318		constructors := make([]types.FuncTemplate, len(cls.Constructors))
319		functions := make([]types.FuncTemplate, len(cls.Functions))
320		fn := cls.FilenameSafe()
321		files = append(files, fn)
322
323		for i, c := range cls.Constructors {
324			c.ReturnValue.AnyType.Type.Name = cls.Name
325			constructors[i] = types.FuncTemplate{
326				Name:  util.ConstructorName(c.Name, cls.Name),
327				CName: c.CIdentifier,
328				Doc:   c.Doc.StringSafe(),
329				Args:  c.Parameters.Template(ns.Name, "", p.Types, c.Throws, types.ArgsFromGoToC),
330				Ret:   c.ReturnValue.Template(ns.Name, "", p.Types, c.Throws),
331			}
332		}
333		signals := make([]types.SignalsTemplate, len(cls.Signals))
334		for i, s := range cls.Signals {
335			signals[i] = types.SignalsTemplate{
336				Doc:      s.Doc.StringSafe(),
337				Name:     util.DashToCamel(s.Name),
338				CName:    s.Name,
339				Args:     s.Parameters.Template(ns.Name, "", p.Types, false, types.ArgsFromCToGo),
340				Ret:      s.ReturnValue.Template(ns.Name, "", p.Types, false),
341				Detailed: s.Detailed,
342			}
343		}
344		receivers := make([]types.FuncTemplate, len(cls.Methods))
345		for i, f := range cls.Methods {
346			name := util.SnakeToCamel(f.Name)
347			implemented[name] = true
348			receivers[i] = types.FuncTemplate{
349				Doc:   f.Doc.StringSafe(),
350				Name:  name,
351				CName: f.CIdentifier,
352				Args:  f.Parameters.Template(ns.Name, "", p.Types, f.Throws, types.ArgsFromGoToC),
353				Ret:   f.ReturnValue.Template(ns.Name, "", p.Types, f.Throws),
354			}
355		}
356		var interfaces []types.InterfaceTemplate
357		for i, f := range cls.Functions {
358			name := fmt.Sprintf("%s%s", util.SnakeToCamel(cls.Name), util.SnakeToCamel(f.Name))
359			functions[i] = types.FuncTemplate{
360				Name:  name,
361				CName: f.CIdentifier,
362				Doc:   f.Doc.StringSafe(),
363				Args:  f.Parameters.Template(ns.Name, "", p.Types, f.Throws, types.ArgsFromGoToC),
364				Ret:   f.ReturnValue.Template(ns.Name, "", p.Types, f.Throws),
365			}
366		}
367		for _, impl := range cls.Implements {
368			interfaces = append(interfaces, types.GetInterfaceFuncs(ns.Name, impl.Name, implemented, p.Types))
369		}
370		properties := make([]types.PropertyTemplate, 0, len(cls.Properties))
371		for _, prop := range cls.Properties {
372			propTemp := prop.Template(ns.Name, p.Types)
373
374			// TODO: Implement non-primitive types, then remove this
375			if propTemp.GValueType != "" {
376				properties = append(properties, propTemp)
377			}
378		}
379		classes[fn] = append(classes[fn], types.ClassTemplate{
380			Doc:          cls.Doc.StringSafe(),
381			Name:         cls.Name,
382			Parent:       util.NormalizeNamespace(ns.Name, cls.Parent, true),
383			Constructors: constructors,
384			Receivers:    receivers,
385			Interfaces:   interfaces,
386			Functions:    functions,
387			Properties:   properties,
388			Signals:      signals,
389			TypeGetter:   cls.GLibGetType,
390		})
391	}
392
393	pkgName := strings.ToLower(ns.Name)
394
395	var pkgConfigName string
396	if len(r.Packages) > 0 {
397		pkgConfigName = r.Packages[0].Name
398	}
399
400	var sharedLibraries []string
401	if ns.SharedLibrary != "" {
402		for _, lib := range strings.Split(ns.SharedLibrary, ",") {
403			if trimmed := strings.TrimSpace(lib); trimmed != "" {
404				sharedLibraries = append(sharedLibraries, trimmed)
405			}
406		}
407	}
408
409	for _, fn := range files {
410		methods := 0
411		for _, i := range interfaces[fn] {
412			methods += len(i.Methods)
413		}
414		for _, i := range records[fn] {
415			methods += len(i.Constructors)
416			methods += len(i.Receivers)
417		}
418		for _, i := range classes[fn] {
419			methods += len(i.Constructors)
420			methods += len(i.Receivers)
421			methods += len(i.Functions)
422		}
423		// we do not need to add the length of interfaces in here
424		// as they should only be loaded when there are classes
425		needsInit := (len(functions[fn]) + methods) > 0
426
427		// Check if any receiver method has callback parameters
428		// This is used to conditionally import unsafe and purego
429		hasReceiverCallbacks := false
430		for _, rec := range records[fn] {
431			for _, r := range rec.Receivers {
432				if len(r.Args.Callbacks) > 0 {
433					hasReceiverCallbacks = true
434					break
435				}
436			}
437			if hasReceiverCallbacks {
438				break
439			}
440		}
441		if !hasReceiverCallbacks {
442			for _, cls := range classes[fn] {
443				for _, r := range cls.Receivers {
444					if len(r.Args.Callbacks) > 0 {
445						hasReceiverCallbacks = true
446						break
447					}
448				}
449				if hasReceiverCallbacks {
450					break
451				}
452			}
453		}
454
455		// Check if any standalone function has callback parameters
456		hasFunctionCallbacks := false
457		for _, f := range functions[fn] {
458			if len(f.Args.Callbacks) > 0 {
459				hasFunctionCallbacks = true
460				break
461			}
462		}
463
464		needsCoreHelpers := false
465		checkFuncArgs := func(funcs []types.FuncTemplate) {
466			if needsCoreHelpers {
467				return
468			}
469			for _, f := range funcs {
470				if f.Args.NeedsCore() {
471					needsCoreHelpers = true
472					return
473				}
474			}
475		}
476		checkInterfaceArgs := func(funcs []types.InterfaceFuncTemplate) {
477			if needsCoreHelpers {
478				return
479			}
480			for _, f := range funcs {
481				if f.Args.NeedsCore() {
482					needsCoreHelpers = true
483					return
484				}
485			}
486		}
487
488		for _, rec := range records[fn] {
489			checkFuncArgs(rec.Constructors)
490			checkFuncArgs(rec.Receivers)
491		}
492		for _, cls := range classes[fn] {
493			checkFuncArgs(cls.Constructors)
494			checkFuncArgs(cls.Receivers)
495			checkFuncArgs(cls.Functions)
496		}
497		checkFuncArgs(functions[fn])
498		for _, inter := range interfaces[fn] {
499			checkInterfaceArgs(inter.Methods)
500		}
501
502		// Check if any signal has string parameters that need core.GoString()
503		hasSignalStrings := false
504		for _, cls := range classes[fn] {
505			for _, sig := range cls.Signals {
506				if sig.Args.HasPureStrings() {
507					hasSignalStrings = true
508					break
509				}
510			}
511			if hasSignalStrings {
512				break
513			}
514		}
515
516		// Scan all types for cross-package references (glib.*, gobject.*, types.*).
517		needsGLib, needsGObject, hasTypeGetters, crossPkgImports := scanCrossPackageRefs(
518			pkgName, functions[fn], records[fn], classes[fn], interfaces[fn])
519		// TypeGetters are emitted by the template when .TypeGetter != "", but
520		// the string "types.GType" only appears in template output — not in the
521		// Go data structures that scanCrossPackageRefs inspects. Check explicitly.
522		if !hasTypeGetters {
523			for _, r := range records[fn] {
524				if r.TypeGetter != "" {
525					hasTypeGetters = true
526					break
527				}
528			}
529		}
530		if !hasTypeGetters {
531			for _, c := range classes[fn] {
532				if c.TypeGetter != "" {
533					hasTypeGetters = true
534					break
535				}
536			}
537		}
538		if !hasTypeGetters {
539			for _, i := range interfaces[fn] {
540				if i.TypeGetter != "" {
541					hasTypeGetters = true
542					break
543				}
544			}
545		}
546		if !hasTypeGetters {
547			for _, a := range aliases[fn] {
548				if a.TypeGetter != "" {
549					hasTypeGetters = true
550					break
551				}
552			}
553		}
554		if !hasTypeGetters {
555			for _, e := range enums[fn] {
556				if e.TypeGetter != "" {
557					hasTypeGetters = true
558					break
559				}
560			}
561		}
562
563		args := types.TemplateArg{
564			PkgName:              pkgName,
565			PkgEnv:               strings.ToUpper(pkgName),
566			PkgConfigName:        pkgConfigName,
567			SharedLibraries:      sharedLibraries,
568			NeedsInit:            needsInit,
569			NeedsCore:            needsCoreHelpers,
570			HasReceiverCallbacks: hasReceiverCallbacks,
571			HasFunctionCallbacks: hasFunctionCallbacks,
572			HasSignalStrings:     hasSignalStrings,
573			HasTypeGetters:       hasTypeGetters,
574			NeedsGLib:            needsGLib,
575			NeedsGObject:         needsGObject,
576			CrossPkgImports:      crossPkgImports,
577			Aliases:              aliases[fn],
578			Callbacks:            callbacks[fn],
579			Records:              records[fn],
580			Enums:                enums[fn],
581			Constants:            constants[fn],
582			Functions:            functions[fn],
583			Interfaces:           interfaces[fn],
584			Classes:              classes[fn],
585		}
586
587		os.MkdirAll(fmt.Sprintf(dir+"/%s", pkgName), 0o755)
588
589		f, err := os.Create(fmt.Sprintf(dir+"/%s/%s", pkgName, fn))
590		if err != nil {
591			panic(err)
592		}
593		err = gotemp.Execute(f, args)
594		if err != nil {
595			panic(err)
596		}
597
598	}
599}
600
601func (p *Pass) Second(dir string, gotemp *template.Template) {
602	for _, r := range p.Parsed {
603		p.writeGo(r, gotemp, dir)
604	}
605}
606
607// scanCrossPackageRefs checks if any generated code in the file references
608// glib.* or gobject.* symbols in pure types, return types, record fields,
609// class parents, signal args, or callback accessor types.
610func scanCrossPackageRefs(
611	pkgName string,
612	funcs []types.FuncTemplate,
613	recs []types.RecordTemplate,
614	classes []types.ClassTemplate,
615	ifaces []types.InterfaceTemplate,
616) (needsGLib, needsGObject, needsTypes bool, crossPkgImports []string) {
617	// Track all cross-package references (e.g. "gio.", "cairo.", "pango.")
618	crossPkgs := make(map[string]bool)
619	check := func(s string) {
620		if !needsGLib && strings.Contains(s, "glib.") {
621			needsGLib = true
622		}
623		if !needsGObject && strings.Contains(s, "gobject.") {
624			needsGObject = true
625		}
626		if !needsTypes && strings.Contains(s, "types.") {
627			needsTypes = true
628		}
629		// Detect any "pkg." references for cross-package imports
630		for _, pkg := range []string{"gio", "cairo", "pango", "pangocairo", "graphene", "gsk", "gdk"} {
631			if strings.Contains(s, pkg+".") {
632				crossPkgs[pkg] = true
633			}
634		}
635	}
636	checkFuncTypes := func(f types.FuncTemplate) {
637		check(f.Ret.Raw)
638		check(f.Ret.Value)
639		// RefSink return types trigger gobject.IncreaseRef in template Fmt()
640		if f.Ret.RefSink {
641			needsGObject = true
642		}
643		// Throws trigger glib.Error in template Preamble()
644		if f.Ret.Throws {
645			needsGLib = true
646		}
647		for _, t := range f.Args.Pure.Types {
648			check(t)
649		}
650		for _, t := range f.Args.API.Types {
651			check(t)
652		}
653	}
654
655	for _, f := range funcs {
656		checkFuncTypes(f)
657		// Callback parameters generate glib.GetCallback/SaveCallbackWithClosure
658		// and gobject.SignalConnect calls in the template output.
659		if len(f.Args.Callbacks) > 0 {
660			needsGLib = true
661		}
662	}
663	for _, r := range recs {
664		for _, f := range r.Fields {
665			check(f.Type)
666		}
667		for _, c := range r.Constructors {
668			checkFuncTypes(c)
669		}
670		for _, m := range r.Receivers {
671			checkFuncTypes(m)
672			if len(m.Args.Callbacks) > 0 {
673				needsGLib = true
674			}
675		}
676		for _, ca := range r.CallbackAccessors {
677			check(ca.CallbackType)
678		}
679	}
680	for _, c := range classes {
681		check(c.Parent)
682		// Signals use glib helpers + gobject.SignalConnect
683		if len(c.Signals) > 0 {
684			needsGLib = true
685			needsGObject = true
686		}
687		for _, con := range c.Constructors {
688			checkFuncTypes(con)
689		}
690		for _, m := range c.Receivers {
691			checkFuncTypes(m)
692		}
693		for _, f := range c.Functions {
694			checkFuncTypes(f)
695		}
696		for _, s := range c.Signals {
697			for _, t := range s.Args.Pure.Types {
698				check(t)
699			}
700			for _, t := range s.Args.API.Types {
701				check(t)
702			}
703		}
704		// Class properties use gobject.Value
705		if len(c.Properties) > 0 {
706			needsGObject = true
707		}
708		// Class interface implementations (e.g. gio.ListModel methods on pango.FontFamily)
709		for _, iface := range c.Interfaces {
710			for _, m := range iface.Methods {
711				checkFuncTypes(m.FuncTemplate)
712				if m.Namespace != "" {
713					check(m.Namespace)
714				}
715			}
716			if len(iface.Properties) > 0 {
717				needsGObject = true
718			}
719		}
720	}
721	for _, i := range ifaces {
722		for _, m := range i.Methods {
723			checkFuncTypes(m.FuncTemplate)
724			// Interface methods have a Namespace prefix (e.g. "gio.")
725			if m.Namespace != "" {
726				check(m.Namespace)
727			}
728		}
729		// Interface properties use gobject.Value/Object for get/set
730		if len(i.Properties) > 0 {
731			needsGObject = true
732			needsGLib = true // glib.StrvGetType etc. used in property vector types
733		}
734		for _, p := range i.Properties {
735			check(p.GoType)
736		}
737		// Interface methods with callbacks need glib for GetCallback/SaveCallbackWithClosure
738		for _, m := range i.Methods {
739			if len(m.FuncTemplate.Args.Callbacks) > 0 {
740				needsGLib = true
741			}
742		}
743	}
744	// A package never needs to import itself, and glib/gobject cannot
745	// import each other (circular dependency).
746	if strings.EqualFold(pkgName, "glib") {
747		needsGLib = false
748		needsGObject = false // glib cannot import gobject (cycle)
749	}
750	if strings.EqualFold(pkgName, "gobject") {
751		needsGObject = false
752		// gobject CAN import glib (no cycle: glib does not import gobject)
753	}
754	delete(crossPkgs, strings.ToLower(pkgName))
755	// glib, gobject, and types are handled by dedicated template conditions
756	delete(crossPkgs, "glib")
757	delete(crossPkgs, "gobject")
758	// Build import paths for detected cross-package references
759	const moduleBase = "github.com/bnema/puregotk/v4/"
760	for pkg := range crossPkgs {
761		crossPkgImports = append(crossPkgImports, moduleBase+pkg)
762	}
763	return
764}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 102

↑↓ moveenter openesc close