...

Source file src/oss.terrastruct.com/d2/d2oracle/edit.go

Documentation: oss.terrastruct.com/d2/d2oracle

     1  package d2oracle
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strconv"
     7  	"strings"
     8  	"unicode"
     9  
    10  	"oss.terrastruct.com/util-go/xdefer"
    11  
    12  	"oss.terrastruct.com/util-go/xrand"
    13  
    14  	"oss.terrastruct.com/util-go/go2"
    15  
    16  	"oss.terrastruct.com/d2/d2ast"
    17  	"oss.terrastruct.com/d2/d2compiler"
    18  	"oss.terrastruct.com/d2/d2format"
    19  	"oss.terrastruct.com/d2/d2graph"
    20  	"oss.terrastruct.com/d2/d2ir"
    21  	"oss.terrastruct.com/d2/d2parser"
    22  	"oss.terrastruct.com/d2/d2target"
    23  )
    24  
    25  type OutsideScopeError struct{}
    26  
    27  func (e OutsideScopeError) Error() string {
    28  	return "operation would modify AST outside of given scope"
    29  }
    30  
    31  func Create(g *d2graph.Graph, boardPath []string, key string) (_ *d2graph.Graph, newKey string, err error) {
    32  	defer xdefer.Errorf(&err, "failed to create %#v", key)
    33  
    34  	boardG := g
    35  	baseAST := g.AST
    36  
    37  	if len(boardPath) > 0 {
    38  		// When compiling a nested board, we can read from boardG but only write to baseBoardG
    39  		boardG = GetBoardGraph(g, boardPath)
    40  		if boardG == nil {
    41  			return nil, "", fmt.Errorf("board %v not found", boardPath)
    42  		}
    43  		// TODO beter name
    44  		baseAST = boardG.BaseAST
    45  	}
    46  
    47  	newKey, edge, err := generateUniqueKey(boardG, key, nil, nil)
    48  	if err != nil {
    49  		return nil, "", err
    50  	}
    51  
    52  	if edge {
    53  		err = _set(boardG, baseAST, key, nil, nil)
    54  	} else {
    55  		err = _set(boardG, baseAST, newKey, nil, nil)
    56  	}
    57  
    58  	if len(boardPath) > 0 {
    59  		replaced := ReplaceBoardNode(g.AST, baseAST, boardPath)
    60  		if !replaced {
    61  			return nil, "", fmt.Errorf("board %v AST not found", boardPath)
    62  		}
    63  	}
    64  
    65  	if err != nil {
    66  		return nil, "", err
    67  	}
    68  	g, err = recompile(g)
    69  	if err != nil {
    70  		return nil, "", err
    71  	}
    72  	return g, newKey, nil
    73  }
    74  
    75  // TODO: update graph in place when compiler can accept single modifications
    76  // TODO: go through all references to decide best spot to insert something
    77  func Set(g *d2graph.Graph, boardPath []string, key string, tag, value *string) (_ *d2graph.Graph, err error) {
    78  	var valueHelp string
    79  	if value == nil {
    80  		valueHelp = fmt.Sprintf("%#v", value)
    81  	} else {
    82  		valueHelp = fmt.Sprintf("%#v", *value)
    83  	}
    84  	if tag != nil {
    85  		defer xdefer.Errorf(&err, "failed to set %#v to %#v %#v", key, *tag, valueHelp)
    86  	} else {
    87  		defer xdefer.Errorf(&err, "failed to set %#v to %#v", key, valueHelp)
    88  	}
    89  
    90  	boardG := g
    91  	baseAST := g.AST
    92  
    93  	if len(boardPath) > 0 {
    94  		// When compiling a nested board, we can read from boardG but only write to baseBoardG
    95  		boardG = GetBoardGraph(g, boardPath)
    96  		if boardG == nil {
    97  			return nil, fmt.Errorf("board %v not found", boardPath)
    98  		}
    99  		// TODO beter name
   100  		baseAST = boardG.BaseAST
   101  	}
   102  
   103  	err = _set(boardG, baseAST, key, tag, value)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  
   108  	if len(boardPath) > 0 {
   109  		replaced := ReplaceBoardNode(g.AST, baseAST, boardPath)
   110  		if !replaced {
   111  			return nil, fmt.Errorf("board %v AST not found", boardPath)
   112  		}
   113  	}
   114  
   115  	return recompile(g)
   116  }
   117  
   118  func ReconnectEdge(g *d2graph.Graph, boardPath []string, edgeKey string, srcKey, dstKey *string) (_ *d2graph.Graph, err error) {
   119  	mk, err := d2parser.ParseMapKey(edgeKey)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	if len(mk.Edges) == 0 {
   125  		return nil, errors.New("edgeKey must be an edge")
   126  	}
   127  
   128  	if mk.EdgeIndex == nil {
   129  		return nil, errors.New("edgeKey must refer to an existing edge")
   130  	}
   131  
   132  	edgeTrimCommon(mk)
   133  
   134  	boardG := g
   135  	baseAST := g.AST
   136  
   137  	if len(boardPath) > 0 {
   138  		// When compiling a nested board, we can read from boardG but only write to baseBoardG
   139  		boardG = GetBoardGraph(g, boardPath)
   140  		if boardG == nil {
   141  			return nil, fmt.Errorf("board %v not found", boardPath)
   142  		}
   143  		// TODO beter name
   144  		baseAST = boardG.BaseAST
   145  	}
   146  
   147  	obj := boardG.Root
   148  	if mk.Key != nil {
   149  		var ok bool
   150  		obj, ok = boardG.Root.HasChild(d2graph.Key(mk.Key))
   151  		if !ok {
   152  			return nil, errors.New("edge not found")
   153  		}
   154  	}
   155  
   156  	edge, ok := obj.HasEdge(mk)
   157  	if !ok {
   158  		return nil, errors.New("edge not found")
   159  	}
   160  
   161  	if srcKey != nil {
   162  		if edge.Src.AbsID() == *srcKey {
   163  			srcKey = nil
   164  		}
   165  	}
   166  
   167  	if dstKey != nil {
   168  		if edge.Dst.AbsID() == *dstKey {
   169  			dstKey = nil
   170  		}
   171  	}
   172  
   173  	if srcKey == nil && dstKey == nil {
   174  		return g, nil
   175  	}
   176  
   177  	var src *d2graph.Object
   178  	var dst *d2graph.Object
   179  	if srcKey != nil {
   180  		srcmk, err := d2parser.ParseMapKey(*srcKey)
   181  		if err != nil {
   182  			return nil, err
   183  		}
   184  		src, ok = boardG.Root.HasChild(d2graph.Key(srcmk.Key))
   185  		if !ok {
   186  			return nil, errors.New("newSrc not found")
   187  		}
   188  	}
   189  	if dstKey != nil {
   190  		dstmk, err := d2parser.ParseMapKey(*dstKey)
   191  		if err != nil {
   192  			return nil, err
   193  		}
   194  		dst, ok = boardG.Root.HasChild(d2graph.Key(dstmk.Key))
   195  		if !ok {
   196  			return nil, errors.New("newDst not found")
   197  		}
   198  	}
   199  
   200  	refs := edge.References
   201  	if baseAST != g.AST {
   202  		refs = getWriteableEdgeRefs(edge, baseAST)
   203  		if len(refs) == 0 || refs[0].ScopeAST != baseAST {
   204  			// TODO null
   205  			return nil, OutsideScopeError{}
   206  		}
   207  	}
   208  	ref := refs[0]
   209  
   210  	// for loops where only one end is changing, node is always ensured
   211  	if edge.Src != edge.Dst && (srcKey == nil || dstKey == nil) {
   212  		var refEdges []*d2ast.Edge
   213  		for _, ref := range refs {
   214  			refEdges = append(refEdges, ref.Edge)
   215  		}
   216  
   217  		if srcKey != nil {
   218  			ensureNode(g, refEdges, ref.ScopeObj, ref.Scope, ref.MapKey, ref.MapKey.Edges[ref.MapKeyEdgeIndex].Src, true)
   219  		}
   220  		if dstKey != nil {
   221  			ensureNode(g, refEdges, ref.ScopeObj, ref.Scope, ref.MapKey, ref.MapKey.Edges[ref.MapKeyEdgeIndex].Dst, false)
   222  		}
   223  	}
   224  
   225  	for i := range refs {
   226  		ref := refs[i]
   227  		// it's a chain
   228  		if len(ref.MapKey.Edges) > 1 && ref.MapKey.EdgeIndex == nil {
   229  			splitChain := true
   230  			// Changing the start of a chain is okay
   231  			if ref.MapKeyEdgeIndex == 0 && dstKey == nil {
   232  				splitChain = false
   233  			}
   234  			// Changing the end of a chain is okay
   235  			if ref.MapKeyEdgeIndex == len(ref.MapKey.Edges)-1 && srcKey == nil {
   236  				splitChain = false
   237  			}
   238  			if splitChain {
   239  				tmp := *ref.MapKey
   240  				mk2 := &tmp
   241  				mk2.Edges = []*d2ast.Edge{ref.MapKey.Edges[ref.MapKeyEdgeIndex]}
   242  				ref.Scope.InsertAfter(ref.MapKey, mk2)
   243  
   244  				if ref.MapKeyEdgeIndex < len(ref.MapKey.Edges)-1 {
   245  					tmp := *ref.MapKey
   246  					mk2 := &tmp
   247  					mk2.Edges = ref.MapKey.Edges[ref.MapKeyEdgeIndex+1:]
   248  					ref.Scope.InsertAfter(ref.MapKey, mk2)
   249  				}
   250  				ref.MapKey.Edges = ref.MapKey.Edges[:ref.MapKeyEdgeIndex]
   251  			}
   252  		}
   253  
   254  		if src != nil {
   255  			srcmk, _ := d2parser.ParseMapKey(*srcKey)
   256  			ref.Edge.Src = srcmk.Key
   257  			newPath, err := pathFromScopeObj(boardG, srcmk, ref.ScopeObj)
   258  			if err != nil {
   259  				return nil, err
   260  			}
   261  			ref.Edge.Src.Path = newPath
   262  		}
   263  		if dst != nil {
   264  			dstmk, _ := d2parser.ParseMapKey(*dstKey)
   265  			ref.Edge.Dst = dstmk.Key
   266  			newPath, err := pathFromScopeObj(boardG, dstmk, ref.ScopeObj)
   267  			if err != nil {
   268  				return nil, err
   269  			}
   270  			ref.Edge.Dst.Path = newPath
   271  		}
   272  	}
   273  
   274  	return recompile(g)
   275  }
   276  
   277  func pathFromScopeKey(g *d2graph.Graph, key *d2ast.Key, scopeak []string) ([]*d2ast.StringBox, error) {
   278  	ak2 := d2graph.Key(key.Key)
   279  
   280  	commonPath := getCommonPath(scopeak, ak2)
   281  
   282  	var newPath []*d2ast.StringBox
   283  	// Move out to most common scope
   284  	for i := len(commonPath); i < len(scopeak); i++ {
   285  		newPath = append(newPath, d2ast.MakeValueBox(d2ast.RawString("_", true)).StringBox())
   286  	}
   287  	// From most common scope, target the toKey
   288  	newPath = append(newPath, key.Key.Path[len(commonPath):]...)
   289  
   290  	return newPath, nil
   291  }
   292  
   293  func pathFromScopeObj(g *d2graph.Graph, key *d2ast.Key, fromScope *d2graph.Object) ([]*d2ast.StringBox, error) {
   294  	// We don't want this to be underscore-resolved scope. We want to ignore underscores
   295  	var scopeak []string
   296  	if fromScope != g.Root {
   297  		scopek, err := d2parser.ParseKey(fromScope.AbsID())
   298  		if err != nil {
   299  			return nil, err
   300  		}
   301  		scopeak = d2graph.Key(scopek)
   302  	}
   303  	return pathFromScopeKey(g, key, scopeak)
   304  }
   305  
   306  func recompile(g *d2graph.Graph) (*d2graph.Graph, error) {
   307  	s := d2format.Format(g.AST)
   308  	g2, _, err := d2compiler.Compile(g.AST.Range.Path, strings.NewReader(s), &d2compiler.CompileOptions{
   309  		FS: g.FS,
   310  	})
   311  	if err != nil {
   312  		return nil, fmt.Errorf("failed to recompile:\n%s\n%w", s, err)
   313  	}
   314  	return g2, nil
   315  }
   316  
   317  // TODO merge flat styles
   318  func _set(g *d2graph.Graph, baseAST *d2ast.Map, key string, tag, value *string) error {
   319  	if tag != nil {
   320  		if hasSpace(*tag) {
   321  			return fmt.Errorf("spaces are not allowed in blockstring tags")
   322  		}
   323  	}
   324  
   325  	mk, err := d2parser.ParseMapKey(key)
   326  	if err != nil {
   327  		return err
   328  	}
   329  
   330  	if len(mk.Edges) > 1 {
   331  		return errors.New("can only set one edge at a time")
   332  	}
   333  
   334  	if value != nil {
   335  		mk.Value = d2ast.MakeValueBox(d2ast.RawString(*value, false))
   336  	} else {
   337  		mk.Value = d2ast.ValueBox{}
   338  	}
   339  	if tag != nil && value != nil {
   340  		mk.Value = d2ast.MakeValueBox(&d2ast.BlockString{
   341  			Tag:   *tag,
   342  			Value: *value,
   343  		})
   344  	}
   345  
   346  	scope := baseAST
   347  	edgeTrimCommon(mk)
   348  	obj := g.Root
   349  	toSkip := 1
   350  
   351  	reserved := false
   352  
   353  	// If you're setting `(x -> y)[0].style.opacity`
   354  	// There's 3 cases you need to handle:
   355  	// 1. The edge has no map.
   356  	// 2. The edge has a style map with opacity not existing
   357  	// 3. The edge has a style map with opacity existing
   358  	//
   359  	// How each case is handled:
   360  	// 1. Append that mapkey to edge.
   361  	// 2. Append opacity to the style map
   362  	// 3. Set opacity
   363  	//
   364  	// There's certainly cleaner code to achieve this, but currently, there's a lot of logic to correctly scope, merge, append.
   365  	// The tests should be comprehensive enough for a safe refactor someday
   366  	//
   367  	// reservedKey = "style"
   368  	// reservedTargetKey = "opacity"
   369  	reservedKey := ""
   370  	reservedTargetKey := ""
   371  	if mk.Key != nil {
   372  		found := true
   373  		for _, idel := range d2graph.Key(mk.Key) {
   374  			_, ok := d2graph.ReservedKeywords[idel]
   375  			if ok {
   376  				reserved = true
   377  				break
   378  			}
   379  			o, ok := obj.HasChild([]string{idel})
   380  			if !ok {
   381  				found = false
   382  				break
   383  			}
   384  			obj = o
   385  
   386  			var maybeNewScope *d2ast.Map
   387  			if baseAST != g.AST {
   388  				writeableRefs := getWriteableRefs(obj, baseAST)
   389  				for _, ref := range writeableRefs {
   390  					if ref.MapKey != nil && ref.MapKey.Value.Map != nil {
   391  						maybeNewScope = ref.MapKey.Value.Map
   392  					}
   393  				}
   394  			} else {
   395  				maybeNewScope = obj.Map
   396  			}
   397  
   398  			if maybeNewScope == nil {
   399  				// If we find a deeper obj.Map we need to skip this key too.
   400  				toSkip++
   401  				continue
   402  			}
   403  
   404  			scope = maybeNewScope
   405  			mk.Key.Path = mk.Key.Path[toSkip:]
   406  			toSkip = 1
   407  			if len(mk.Key.Path) == 0 {
   408  				mk.Key = nil
   409  			}
   410  		}
   411  
   412  		var objK *d2ast.Key
   413  		if baseAST != g.AST {
   414  			writeableRefs := getWriteableRefs(obj, baseAST)
   415  			if len(writeableRefs) > 0 {
   416  				objK = writeableRefs[0].MapKey
   417  			}
   418  			if objK == nil {
   419  				appendMapKey(scope, mk)
   420  				return nil
   421  			}
   422  		}
   423  		var m *d2ast.Map
   424  		if objK != nil {
   425  			m = objK.Value.Map
   426  		} else {
   427  			m = obj.Map
   428  		}
   429  
   430  		if obj.Label.MapKey != nil && m == nil && (!found || reserved || len(mk.Edges) > 0) {
   431  			m2 := &d2ast.Map{
   432  				Range: d2ast.MakeRange(",1:0:0-1:0:0"),
   433  			}
   434  			if objK == nil {
   435  				obj.Map = m2
   436  				objK = obj.Label.MapKey
   437  			} else {
   438  				objK.Value.Map = m2
   439  			}
   440  			objK.Primary = objK.Value.ScalarBox()
   441  			objK.Value = d2ast.MakeValueBox(m2)
   442  			scope = m2
   443  
   444  			mk.Key.Path = mk.Key.Path[toSkip-1:]
   445  			toSkip = 1
   446  			if len(mk.Key.Path) == 0 {
   447  				mk.Key = nil
   448  			}
   449  		}
   450  
   451  		if !found {
   452  			appendMapKey(scope, mk)
   453  			return nil
   454  		}
   455  	}
   456  	ir, err := d2ir.Compile(g.AST, &d2ir.CompileOptions{
   457  		FS: g.FS,
   458  	})
   459  	if err != nil {
   460  		return err
   461  	}
   462  	attrs := obj.Attributes
   463  	var edge *d2graph.Edge
   464  	if len(mk.Edges) == 1 {
   465  		if mk.EdgeIndex == nil {
   466  			appendMapKey(scope, mk)
   467  			return nil
   468  		}
   469  		var ok bool
   470  		edge, ok = obj.HasEdge(mk)
   471  		if !ok {
   472  			return errors.New("edge not found")
   473  		}
   474  		refs := edge.References
   475  		if baseAST != g.AST {
   476  			refs = getWriteableEdgeRefs(edge, baseAST)
   477  		}
   478  		onlyInChain := true
   479  		for _, ref := range refs {
   480  			// TODO merge flat edgekeys
   481  			// E.g. this can group into a map
   482  			// (y -> z)[0].style.opacity: 0.4
   483  			// (y -> z)[0].style.animated: true
   484  			if len(ref.MapKey.Edges) == 1 {
   485  				if ref.MapKey.EdgeIndex == nil || ref.MapKey.Value.Map != nil {
   486  					onlyInChain = false
   487  				}
   488  			}
   489  			// If a ref has an exact match on this key, just change the value
   490  			tmp1 := *ref.MapKey
   491  			tmp2 := *mk
   492  			noVal1 := &tmp1
   493  			noVal2 := &tmp2
   494  			noVal1.Value = d2ast.ValueBox{}
   495  			noVal2.Value = d2ast.ValueBox{}
   496  			if noVal1.D2OracleEquals(noVal2) {
   497  				ref.MapKey.Value = mk.Value
   498  				return nil
   499  			}
   500  		}
   501  		if onlyInChain {
   502  			appendMapKey(scope, mk)
   503  			return nil
   504  		}
   505  		attrs = edge.Attributes
   506  
   507  		if mk.EdgeKey != nil {
   508  			if _, ok := d2graph.ReservedKeywords[mk.EdgeKey.Path[0].Unbox().ScalarString()]; !ok {
   509  				return errors.New("edge key must be reserved")
   510  			}
   511  			reserved = true
   512  
   513  			toSkip = 1
   514  			mk = &d2ast.Key{
   515  				Key:   cloneKey(mk.EdgeKey),
   516  				Value: mk.Value,
   517  			}
   518  
   519  			foundMap := false
   520  			for _, ref := range refs {
   521  				// TODO get the most nested one
   522  				if ref.MapKey.Value.Map != nil {
   523  					foundMap = true
   524  					scope = ref.MapKey.Value.Map
   525  					for _, n := range scope.Nodes {
   526  						if n.MapKey.Value.Map == nil {
   527  							continue
   528  						}
   529  						if n.MapKey.Key == nil || len(n.MapKey.Key.Path) != 1 {
   530  							continue
   531  						}
   532  						if n.MapKey.Key.Path[0].Unbox().ScalarString() == mk.Key.Path[toSkip-1].Unbox().ScalarString() {
   533  							scope = n.MapKey.Value.Map
   534  							if mk.Key.Path[0].Unbox().ScalarString() == "source-arrowhead" && edge.SrcArrowhead != nil {
   535  								attrs = *edge.SrcArrowhead
   536  							}
   537  							if mk.Key.Path[0].Unbox().ScalarString() == "target-arrowhead" && edge.DstArrowhead != nil {
   538  								attrs = *edge.DstArrowhead
   539  							}
   540  							reservedKey = mk.Key.Path[0].Unbox().ScalarString()
   541  							mk.Key.Path = mk.Key.Path[1:]
   542  							reservedTargetKey = mk.Key.Path[0].Unbox().ScalarString()
   543  							break
   544  						}
   545  					}
   546  					break
   547  				}
   548  			}
   549  			if !foundMap && attrs.Label.MapKey != nil {
   550  				attrs.Label.MapKey.Primary = attrs.Label.MapKey.Value.ScalarBox()
   551  				edgeMap := &d2ast.Map{
   552  					Range: d2ast.MakeRange(",1:0:0-1:0:0"),
   553  				}
   554  				attrs.Label.MapKey.Value = d2ast.MakeValueBox(edgeMap)
   555  				scope = edgeMap
   556  			}
   557  		}
   558  	}
   559  
   560  	if reserved {
   561  		inlined := func(s *d2graph.Scalar) bool {
   562  			if s != nil && s.MapKey != nil {
   563  				// The value was set outside of what's writeable
   564  				if s.MapKey.Range.Path != baseAST.Range.Path {
   565  					return false
   566  				}
   567  			}
   568  			return s != nil && s.MapKey != nil && !ir.InClass(s.MapKey)
   569  		}
   570  		reservedIndex := toSkip - 1
   571  		if mk.Key != nil && len(mk.Key.Path) > 0 {
   572  			if reservedKey == "" {
   573  				reservedKey = mk.Key.Path[reservedIndex].Unbox().ScalarString()
   574  			}
   575  			switch reservedKey {
   576  			case "shape":
   577  				if inlined(&attrs.Shape) {
   578  					attrs.Shape.MapKey.SetScalar(mk.Value.ScalarBox())
   579  					return nil
   580  				}
   581  			case "link":
   582  				if inlined(attrs.Link) {
   583  					attrs.Link.MapKey.SetScalar(mk.Value.ScalarBox())
   584  					return nil
   585  				}
   586  			case "tooltip":
   587  				if inlined(attrs.Tooltip) {
   588  					attrs.Tooltip.MapKey.SetScalar(mk.Value.ScalarBox())
   589  					return nil
   590  				}
   591  			case "width":
   592  				if inlined(attrs.WidthAttr) {
   593  					attrs.WidthAttr.MapKey.SetScalar(mk.Value.ScalarBox())
   594  					return nil
   595  				}
   596  			case "height":
   597  				if inlined(attrs.HeightAttr) {
   598  					attrs.HeightAttr.MapKey.SetScalar(mk.Value.ScalarBox())
   599  					return nil
   600  				}
   601  			case "top":
   602  				if inlined(attrs.Top) {
   603  					attrs.Top.MapKey.SetScalar(mk.Value.ScalarBox())
   604  					return nil
   605  				}
   606  			case "left":
   607  				if inlined(attrs.Left) {
   608  					attrs.Left.MapKey.SetScalar(mk.Value.ScalarBox())
   609  					return nil
   610  				}
   611  			case "grid-rows":
   612  				if inlined(attrs.GridRows) {
   613  					attrs.GridRows.MapKey.SetScalar(mk.Value.ScalarBox())
   614  					return nil
   615  				}
   616  			case "grid-columns":
   617  				if inlined(attrs.GridColumns) {
   618  					attrs.GridColumns.MapKey.SetScalar(mk.Value.ScalarBox())
   619  					return nil
   620  				}
   621  			case "grid-gap":
   622  				if inlined(attrs.GridGap) {
   623  					attrs.GridGap.MapKey.SetScalar(mk.Value.ScalarBox())
   624  					return nil
   625  				}
   626  			case "vertical-gap":
   627  				if inlined(attrs.VerticalGap) {
   628  					attrs.VerticalGap.MapKey.SetScalar(mk.Value.ScalarBox())
   629  					return nil
   630  				}
   631  			case "horizontal-gap":
   632  				if inlined(attrs.HorizontalGap) {
   633  					attrs.HorizontalGap.MapKey.SetScalar(mk.Value.ScalarBox())
   634  					return nil
   635  				}
   636  			case "source-arrowhead", "target-arrowhead":
   637  				var arrowhead *d2graph.Attributes
   638  				if reservedKey == "source-arrowhead" {
   639  					arrowhead = edge.SrcArrowhead
   640  				} else {
   641  					arrowhead = edge.DstArrowhead
   642  				}
   643  				if arrowhead != nil {
   644  					if reservedTargetKey == "" {
   645  						if len(mk.Key.Path[reservedIndex:]) != 2 {
   646  							return errors.New("malformed style setting, expected 2 part path")
   647  						}
   648  						reservedTargetKey = mk.Key.Path[reservedIndex+1].Unbox().ScalarString()
   649  					}
   650  					switch reservedTargetKey {
   651  					case "shape":
   652  						if inlined(&arrowhead.Shape) {
   653  							arrowhead.Shape.MapKey.SetScalar(mk.Value.ScalarBox())
   654  							return nil
   655  						}
   656  					case "label":
   657  						if inlined(&arrowhead.Label) {
   658  							arrowhead.Label.MapKey.SetScalar(mk.Value.ScalarBox())
   659  							return nil
   660  						}
   661  					}
   662  				}
   663  			case "style":
   664  				if reservedTargetKey == "" {
   665  					if len(mk.Key.Path[reservedIndex:]) != 2 {
   666  						return errors.New("malformed style setting, expected 2 part path")
   667  					}
   668  					reservedTargetKey = mk.Key.Path[reservedIndex+1].Unbox().ScalarString()
   669  				}
   670  				switch reservedTargetKey {
   671  				case "opacity":
   672  					if inlined(attrs.Style.Opacity) {
   673  						attrs.Style.Opacity.MapKey.SetScalar(mk.Value.ScalarBox())
   674  						return nil
   675  					}
   676  				case "stroke":
   677  					if inlined(attrs.Style.Stroke) {
   678  						attrs.Style.Stroke.MapKey.SetScalar(mk.Value.ScalarBox())
   679  						return nil
   680  					}
   681  				case "fill":
   682  					if inlined(attrs.Style.Fill) {
   683  						attrs.Style.Fill.MapKey.SetScalar(mk.Value.ScalarBox())
   684  						return nil
   685  					}
   686  				case "stroke-width":
   687  					if inlined(attrs.Style.StrokeWidth) {
   688  						attrs.Style.StrokeWidth.MapKey.SetScalar(mk.Value.ScalarBox())
   689  						return nil
   690  					}
   691  				case "stroke-dash":
   692  					if inlined(attrs.Style.StrokeDash) {
   693  						attrs.Style.StrokeDash.MapKey.SetScalar(mk.Value.ScalarBox())
   694  						return nil
   695  					}
   696  				case "border-radius":
   697  					if inlined(attrs.Style.BorderRadius) {
   698  						attrs.Style.BorderRadius.MapKey.SetScalar(mk.Value.ScalarBox())
   699  						return nil
   700  					}
   701  				case "shadow":
   702  					if inlined(attrs.Style.Shadow) {
   703  						attrs.Style.Shadow.MapKey.SetScalar(mk.Value.ScalarBox())
   704  						return nil
   705  					}
   706  				case "3d":
   707  					if inlined(attrs.Style.ThreeDee) {
   708  						attrs.Style.ThreeDee.MapKey.SetScalar(mk.Value.ScalarBox())
   709  						return nil
   710  					}
   711  				case "multiple":
   712  					if inlined(attrs.Style.Multiple) {
   713  						attrs.Style.Multiple.MapKey.SetScalar(mk.Value.ScalarBox())
   714  						return nil
   715  					}
   716  				case "double-border":
   717  					if inlined(attrs.Style.DoubleBorder) {
   718  						attrs.Style.DoubleBorder.MapKey.SetScalar(mk.Value.ScalarBox())
   719  						return nil
   720  					}
   721  				case "font":
   722  					if inlined(attrs.Style.Font) {
   723  						attrs.Style.Font.MapKey.SetScalar(mk.Value.ScalarBox())
   724  						return nil
   725  					}
   726  				case "font-size":
   727  					if inlined(attrs.Style.FontSize) {
   728  						attrs.Style.FontSize.MapKey.SetScalar(mk.Value.ScalarBox())
   729  						return nil
   730  					}
   731  				case "font-color":
   732  					if inlined(attrs.Style.FontColor) {
   733  						attrs.Style.FontColor.MapKey.SetScalar(mk.Value.ScalarBox())
   734  						return nil
   735  					}
   736  				case "animated":
   737  					if inlined(attrs.Style.Animated) {
   738  						attrs.Style.Animated.MapKey.SetScalar(mk.Value.ScalarBox())
   739  						return nil
   740  					}
   741  				case "bold":
   742  					if inlined(attrs.Style.Bold) {
   743  						attrs.Style.Bold.MapKey.SetScalar(mk.Value.ScalarBox())
   744  						return nil
   745  					}
   746  				case "italic":
   747  					if inlined(attrs.Style.Italic) {
   748  						attrs.Style.Italic.MapKey.SetScalar(mk.Value.ScalarBox())
   749  						return nil
   750  					}
   751  				case "underline":
   752  					if inlined(attrs.Style.Underline) {
   753  						attrs.Style.Underline.MapKey.SetScalar(mk.Value.ScalarBox())
   754  						return nil
   755  					}
   756  				case "fill-pattern":
   757  					if inlined(attrs.Style.FillPattern) {
   758  						attrs.Style.FillPattern.MapKey.SetScalar(mk.Value.ScalarBox())
   759  						return nil
   760  					}
   761  				}
   762  			case "label":
   763  				if inlined(&attrs.Label) {
   764  					attrs.Label.MapKey.SetScalar(mk.Value.ScalarBox())
   765  					return nil
   766  				}
   767  			}
   768  		}
   769  	} else if attrs.Label.MapKey != nil {
   770  		attrs.Label.MapKey.SetScalar(mk.Value.ScalarBox())
   771  		return nil
   772  	}
   773  	appendMapKey(scope, mk)
   774  	return nil
   775  }
   776  
   777  func appendUniqueMapKey(m *d2ast.Map, mk *d2ast.Key) {
   778  	for _, n := range m.Nodes {
   779  		if n.MapKey != nil && n.MapKey.D2OracleEquals(mk) {
   780  			return
   781  		}
   782  	}
   783  	appendMapKey(m, mk)
   784  }
   785  
   786  func appendMapKey(m *d2ast.Map, mk *d2ast.Key) {
   787  	m.Nodes = append(m.Nodes, d2ast.MapNodeBox{
   788  		MapKey: mk,
   789  	})
   790  	if len(m.Nodes) == 1 &&
   791  		mk.Key != nil &&
   792  		len(mk.Key.Path) > 0 {
   793  		_, ok := d2graph.ReservedKeywords[mk.Key.Path[0].Unbox().ScalarString()]
   794  		if ok {
   795  			// Allow one line reserved key (like shape) maps.
   796  			// TODO: This needs to be smarter as certain keys are only reserved in context.
   797  			// e.g. all keys under style are reserved. And constraint is only reserved
   798  			// under sql_table shapes.
   799  			return
   800  		}
   801  	}
   802  	if !m.IsFileMap() && m.Range.OneLine() {
   803  		// This doesn't require any shenanigans to prevent consuming sibling spacing because
   804  		// d2format will use the mapkey's range to determine whether to insert extra newlines.
   805  		// See TestCreate/make_scope_multiline_spacing_2
   806  		m.Range.End.Line++
   807  	}
   808  }
   809  
   810  func Delete(g *d2graph.Graph, boardPath []string, key string) (_ *d2graph.Graph, err error) {
   811  	defer xdefer.Errorf(&err, "failed to delete %#v", key)
   812  
   813  	mk, err := d2parser.ParseMapKey(key)
   814  	if err != nil {
   815  		return nil, err
   816  	}
   817  
   818  	if len(mk.Edges) > 1 {
   819  		return nil, errors.New("can only delete one edge at a time")
   820  	}
   821  
   822  	if len(mk.Edges) == 1 {
   823  		edgeTrimCommon(mk)
   824  	}
   825  
   826  	boardG := g
   827  	baseAST := g.AST
   828  
   829  	if len(boardPath) > 0 {
   830  		// When compiling a nested board, we can read from boardG but only write to baseBoardG
   831  		boardG = GetBoardGraph(g, boardPath)
   832  		if boardG == nil {
   833  			return nil, fmt.Errorf("board %v not found", boardPath)
   834  		}
   835  		// TODO beter name
   836  		baseAST = boardG.BaseAST
   837  	}
   838  
   839  	g2, err := deleteReserved(g, mk)
   840  	if err != nil {
   841  		return nil, err
   842  	}
   843  	if g != g2 {
   844  		return g2, nil
   845  	}
   846  
   847  	if len(mk.Edges) == 1 {
   848  		obj := boardG.Root
   849  		if mk.Key != nil {
   850  			var ok bool
   851  			obj, ok = boardG.Root.HasChild(d2graph.Key(mk.Key))
   852  			if !ok {
   853  				return g, nil
   854  			}
   855  		}
   856  		e, ok := obj.HasEdge(mk)
   857  		if !ok {
   858  			return g, nil
   859  		}
   860  
   861  		refs := e.References
   862  		if len(boardPath) > 0 {
   863  			refs := getWriteableEdgeRefs(e, baseAST)
   864  			if len(refs) != len(e.References) {
   865  				mk.Value = d2ast.MakeValueBox(&d2ast.Null{})
   866  			}
   867  		}
   868  
   869  		if _, ok := mk.Value.Unbox().(*d2ast.Null); !ok {
   870  			ref := refs[0]
   871  			var refEdges []*d2ast.Edge
   872  			for _, ref := range refs {
   873  				refEdges = append(refEdges, ref.Edge)
   874  			}
   875  			ensureNode(g, refEdges, ref.ScopeObj, ref.Scope, ref.MapKey, ref.MapKey.Edges[ref.MapKeyEdgeIndex].Src, true)
   876  			ensureNode(g, refEdges, ref.ScopeObj, ref.Scope, ref.MapKey, ref.MapKey.Edges[ref.MapKeyEdgeIndex].Dst, false)
   877  
   878  			for i := len(e.References) - 1; i >= 0; i-- {
   879  				ref := e.References[i]
   880  				deleteEdge(g, ref.Scope, ref.MapKey, ref.MapKeyEdgeIndex)
   881  			}
   882  
   883  			edges, ok := obj.FindEdges(mk)
   884  			if ok {
   885  				for _, e2 := range edges {
   886  					if e2.Index <= e.Index {
   887  						continue
   888  					}
   889  					for i := len(e2.References) - 1; i >= 0; i-- {
   890  						ref := e2.References[i]
   891  						if ref.MapKey.EdgeIndex != nil {
   892  							*ref.MapKey.EdgeIndex.Int--
   893  						}
   894  					}
   895  				}
   896  			}
   897  		} else {
   898  			// NOTE: it only needs to be after the last ref, but perhaps simplest and cleanest to append all nulls at the end
   899  			appendMapKey(baseAST, mk)
   900  		}
   901  		if len(boardPath) > 0 {
   902  			replaced := ReplaceBoardNode(g.AST, baseAST, boardPath)
   903  			if !replaced {
   904  				return nil, fmt.Errorf("board %v AST not found", boardPath)
   905  			}
   906  			return recompile(g)
   907  		}
   908  		return recompile(boardG)
   909  	}
   910  
   911  	prevG, _ := recompile(boardG)
   912  
   913  	boardG, err = renameConflictsToParent(boardG, mk.Key)
   914  	if err != nil {
   915  		return nil, err
   916  	}
   917  
   918  	obj, ok := boardG.Root.HasChild(d2graph.Key(mk.Key))
   919  	if !ok {
   920  		return g, nil
   921  	}
   922  
   923  	if len(boardPath) > 0 {
   924  		writeableRefs := getWriteableRefs(obj, baseAST)
   925  		if len(writeableRefs) != len(obj.References) {
   926  			mk.Value = d2ast.MakeValueBox(&d2ast.Null{})
   927  		}
   928  	}
   929  
   930  	if _, ok := mk.Value.Unbox().(*d2ast.Null); !ok {
   931  		boardG, err = deleteObject(boardG, baseAST, mk.Key, obj)
   932  		if err != nil {
   933  			return nil, err
   934  		}
   935  
   936  		if err := updateNear(prevG, boardG, &key, nil, false); err != nil {
   937  			return nil, err
   938  		}
   939  	} else {
   940  		appendMapKey(baseAST, mk)
   941  	}
   942  
   943  	if len(boardPath) > 0 {
   944  		replaced := ReplaceBoardNode(g.AST, baseAST, boardPath)
   945  		if !replaced {
   946  			return nil, fmt.Errorf("board %v AST not found", boardPath)
   947  		}
   948  		return recompile(g)
   949  	}
   950  
   951  	return recompile(boardG)
   952  }
   953  
   954  func bumpChildrenUnderscores(m *d2ast.Map) {
   955  	for _, n := range m.Nodes {
   956  		if n.MapKey == nil {
   957  			continue
   958  		}
   959  		if n.MapKey.Key != nil {
   960  			if n.MapKey.Key.Path[0].Unbox().ScalarString() == "_" {
   961  				n.MapKey.Key.Path = n.MapKey.Key.Path[1:]
   962  			}
   963  		}
   964  		for _, e := range n.MapKey.Edges {
   965  			if e.Src.Path[0].Unbox().ScalarString() == "_" {
   966  				e.Src.Path = e.Src.Path[1:]
   967  			}
   968  			if e.Dst.Path[0].Unbox().ScalarString() == "_" {
   969  				e.Dst.Path = e.Dst.Path[1:]
   970  			}
   971  		}
   972  		if n.MapKey.Value.Map != nil {
   973  			bumpChildrenUnderscores(n.MapKey.Value.Map)
   974  		}
   975  	}
   976  }
   977  
   978  func hoistRefChildren(g *d2graph.Graph, key *d2ast.KeyPath, ref d2graph.Reference) {
   979  	if ref.MapKey.Value.Map == nil {
   980  		return
   981  	}
   982  
   983  	bumpChildrenUnderscores(ref.MapKey.Value.Map)
   984  	scopeKey, scope := findNearestParentScope(g, key)
   985  	for i := 0; i < len(ref.MapKey.Value.Map.Nodes); i++ {
   986  		n := ref.MapKey.Value.Map.Nodes[i]
   987  		if n.MapKey == nil {
   988  			continue
   989  		}
   990  		if n.MapKey.Key != nil {
   991  			_, ok := d2graph.ReservedKeywords[n.MapKey.Key.Path[0].Unbox().ScalarString()]
   992  			if ok {
   993  				continue
   994  			}
   995  		}
   996  		scopeKey := cloneKey(scopeKey)
   997  		scopeKey.Path = scopeKey.Path[:len(scopeKey.Path)-1]
   998  		if n.MapKey.Key != nil {
   999  			scopeKey.Path = append(scopeKey.Path, n.MapKey.Key.Path...)
  1000  		}
  1001  		if len(scopeKey.Path) > 0 {
  1002  			n.MapKey.Key = scopeKey
  1003  		}
  1004  		scope.InsertBefore(ref.MapKey, n.Unbox())
  1005  	}
  1006  }
  1007  
  1008  // renameConflictsToParent renames would-be ID conflicts.
  1009  func renameConflictsToParent(g *d2graph.Graph, key *d2ast.KeyPath) (*d2graph.Graph, error) {
  1010  	obj, ok := g.Root.HasChild(d2graph.Key(key))
  1011  	if !ok {
  1012  		return g, nil
  1013  	}
  1014  	if obj.Shape.Value == d2target.ShapeSQLTable || obj.Shape.Value == d2target.ShapeClass {
  1015  		return g, nil
  1016  	}
  1017  
  1018  	// Usually ignore the object when generating, but if a sibling has the same ID, can't ignore
  1019  	ignored := obj
  1020  	for _, ch := range obj.ChildrenArray {
  1021  		if ch.ID == obj.ID {
  1022  			ignored = nil
  1023  			break
  1024  		}
  1025  	}
  1026  
  1027  	// Keep a list of newly generated IDs, so that generateUniqueKey considers them for conflict
  1028  	var newIDs []string
  1029  	// If we already renamed the key from another reference, no need to touch
  1030  	dedupedRenames := map[string]struct{}{}
  1031  	for _, ref := range obj.References {
  1032  		var absKeys []*d2ast.KeyPath
  1033  
  1034  		if len(ref.Key.Path)-1 == ref.KeyPathIndex {
  1035  			if ref.MapKey.Value.Map == nil {
  1036  				continue
  1037  			}
  1038  			var mapKeys []*d2ast.KeyPath
  1039  			for _, n := range ref.MapKey.Value.Map.Nodes {
  1040  				if n.MapKey == nil {
  1041  					continue
  1042  				}
  1043  				if n.MapKey.Key != nil {
  1044  					_, ok := d2graph.ReservedKeywords[n.MapKey.Key.Path[0].Unbox().ScalarString()]
  1045  					if ok {
  1046  						continue
  1047  					}
  1048  					mapKeys = append(mapKeys, n.MapKey.Key)
  1049  				}
  1050  				for _, e := range n.MapKey.Edges {
  1051  					mapKeys = append(mapKeys, e.Src)
  1052  					mapKeys = append(mapKeys, e.Dst)
  1053  				}
  1054  			}
  1055  			for _, k := range mapKeys {
  1056  				absKey, err := d2parser.ParseKey(ref.ScopeObj.AbsID())
  1057  				if err != nil {
  1058  					absKey = &d2ast.KeyPath{}
  1059  				}
  1060  				absKey.Path = append(absKey.Path, ref.Key.Path...)
  1061  				absKey.Path = append(absKey.Path, k.Path[0])
  1062  				absKeys = append(absKeys, absKey)
  1063  			}
  1064  		} else if _, ok := d2graph.ReservedKeywords[ref.Key.Path[len(ref.Key.Path)-1].Unbox().ScalarString()]; !ok {
  1065  			absKey, err := d2parser.ParseKey(ref.ScopeObj.AbsID())
  1066  			if err != nil {
  1067  				absKey = &d2ast.KeyPath{}
  1068  			}
  1069  			absKey.Path = append(absKey.Path, ref.Key.Path[:ref.KeyPathIndex+2]...)
  1070  			absKeys = append(absKeys, absKey)
  1071  		}
  1072  
  1073  		renames := make(map[string]string)
  1074  		for _, absKey := range absKeys {
  1075  			ida := d2graph.Key(absKey)
  1076  			absKeyStr := strings.Join(ida, ".")
  1077  			if _, ok := dedupedRenames[absKeyStr]; ok {
  1078  				continue
  1079  			}
  1080  			// Stale reference
  1081  			dedupedRenames[absKeyStr] = struct{}{}
  1082  			// Do not consider the parent for conflicts, assume the parent will be deleted
  1083  			if ida[len(ida)-1] == ida[len(ida)-2] {
  1084  				continue
  1085  			}
  1086  
  1087  			hoistedAbsKey, err := d2parser.ParseKey(ref.ScopeObj.AbsID())
  1088  			if err != nil {
  1089  				hoistedAbsKey = &d2ast.KeyPath{}
  1090  			}
  1091  			hoistedAbsKey.Path = append(hoistedAbsKey.Path, ref.Key.Path[:ref.KeyPathIndex]...)
  1092  			hoistedAbsKey.Path = append(hoistedAbsKey.Path, absKey.Path[len(absKey.Path)-1])
  1093  
  1094  			// Can't generate a key that'd conflict with sibling
  1095  			var siblingHoistedIDs []string
  1096  			for _, otherAbsKey := range absKeys {
  1097  				if absKey == otherAbsKey {
  1098  					continue
  1099  				}
  1100  				ida := d2graph.Key(otherAbsKey)
  1101  				absKeyStr := strings.Join(ida, ".")
  1102  				if _, ok := dedupedRenames[absKeyStr]; ok {
  1103  					continue
  1104  				}
  1105  				hoistedAbsKey, err := d2parser.ParseKey(ref.ScopeObj.AbsID())
  1106  				if err != nil {
  1107  					hoistedAbsKey = &d2ast.KeyPath{}
  1108  				}
  1109  				hoistedAbsKey.Path = append(hoistedAbsKey.Path, ref.Key.Path[:ref.KeyPathIndex]...)
  1110  				hoistedAbsKey.Path = append(hoistedAbsKey.Path, otherAbsKey.Path[len(otherAbsKey.Path)-1])
  1111  				siblingHoistedIDs = append(siblingHoistedIDs, strings.Join(d2graph.Key(hoistedAbsKey), "."))
  1112  			}
  1113  
  1114  			uniqueKeyStr, _, err := generateUniqueKey(g, strings.Join(d2graph.Key(hoistedAbsKey), "."), ignored, append(newIDs, siblingHoistedIDs...))
  1115  			if err != nil {
  1116  				return nil, err
  1117  			}
  1118  			newIDs = append(newIDs, uniqueKeyStr)
  1119  			uniqueKey, err := d2parser.ParseKey(uniqueKeyStr)
  1120  			if err != nil {
  1121  				return nil, err
  1122  			}
  1123  
  1124  			renamedKey := cloneKey(absKey)
  1125  			renamedKey.Path[len(renamedKey.Path)-1].Unbox().SetString(uniqueKey.Path[len(uniqueKey.Path)-1].Unbox().ScalarString())
  1126  
  1127  			renamedKeyStr := strings.Join(d2graph.Key(renamedKey), ".")
  1128  			if absKeyStr != renamedKeyStr {
  1129  				renames[absKeyStr] = renamedKeyStr
  1130  			}
  1131  			dedupedRenames[renamedKeyStr] = struct{}{}
  1132  		}
  1133  		// We need to rename in a conflict-free order
  1134  		// E.g. imagine you have children `Text 4` and `Text`.
  1135  		// `Text 4` would get renamed to `Text` and `Text` gets renamed to `Text 2`
  1136  		// But if we follow that order, then both would get named to `Text 2`
  1137  		// So order such that the ones that have a conflict are done last, after the no-conflict ones are done
  1138  		// A cycle would never occur, as the uniqueness constraint is guaranteed
  1139  		var renameOrder []string
  1140  		for k, v := range renames {
  1141  			// conflict
  1142  			if _, ok := renames[v]; ok {
  1143  				renameOrder = append(renameOrder, k)
  1144  			} else {
  1145  				renameOrder = append([]string{k}, renameOrder...)
  1146  			}
  1147  		}
  1148  		for _, k := range renameOrder {
  1149  			var err error
  1150  			// TODO boardPath
  1151  			g, err = move(g, nil, k, renames[k], false)
  1152  			if err != nil {
  1153  				return nil, err
  1154  			}
  1155  		}
  1156  	}
  1157  	return g, nil
  1158  }
  1159  
  1160  func deleteReserved(g *d2graph.Graph, mk *d2ast.Key) (*d2graph.Graph, error) {
  1161  	targetKey := mk.Key
  1162  	if len(mk.Edges) == 1 {
  1163  		if mk.EdgeKey == nil {
  1164  			return g, nil
  1165  		}
  1166  		targetKey = mk.EdgeKey
  1167  	}
  1168  	_, ok := d2graph.ReservedKeywords[targetKey.Path[len(targetKey.Path)-1].Unbox().ScalarString()]
  1169  	if !ok {
  1170  		return g, nil
  1171  	}
  1172  
  1173  	var e *d2graph.Edge
  1174  	obj := g.Root
  1175  	if len(mk.Edges) == 1 {
  1176  		if mk.Key != nil {
  1177  			var ok bool
  1178  			obj, ok = g.Root.HasChild(d2graph.Key(mk.Key))
  1179  			if !ok {
  1180  				return g, nil
  1181  			}
  1182  		}
  1183  		e, ok = obj.HasEdge(mk)
  1184  		if !ok {
  1185  			return g, nil
  1186  		}
  1187  
  1188  		if err := deleteEdgeField(g, e, targetKey.Path[len(targetKey.Path)-1].Unbox().ScalarString()); err != nil {
  1189  			return nil, err
  1190  		}
  1191  		return recompile(g)
  1192  	}
  1193  
  1194  	isStyleKey := false
  1195  	for _, id := range d2graph.Key(targetKey) {
  1196  		_, ok := d2graph.ReservedKeywords[id]
  1197  		if ok {
  1198  			if id == "style" {
  1199  				isStyleKey = true
  1200  				continue
  1201  			}
  1202  			if isStyleKey {
  1203  				err := deleteObjField(g, obj, id)
  1204  				if err != nil {
  1205  					return nil, err
  1206  				}
  1207  			}
  1208  
  1209  			if id == "near" ||
  1210  				id == "tooltip" ||
  1211  				id == "icon" ||
  1212  				id == "width" ||
  1213  				id == "height" ||
  1214  				id == "left" ||
  1215  				id == "top" ||
  1216  				id == "link" {
  1217  				err := deleteObjField(g, obj, id)
  1218  				if err != nil {
  1219  					return nil, err
  1220  				}
  1221  			}
  1222  			break
  1223  		}
  1224  		obj, ok = obj.HasChild([]string{id})
  1225  		if !ok {
  1226  			return nil, fmt.Errorf("object not found")
  1227  		}
  1228  	}
  1229  
  1230  	return recompile(g)
  1231  }
  1232  
  1233  func deleteMapField(m *d2ast.Map, field string) {
  1234  	for i := 0; i < len(m.Nodes); i++ {
  1235  		n := m.Nodes[i]
  1236  		if n.MapKey != nil && n.MapKey.Key != nil {
  1237  			if n.MapKey.Key.Path[0].Unbox().ScalarString() == field {
  1238  				deleteFromMap(m, n.MapKey)
  1239  			} else if n.MapKey.Key.Path[0].Unbox().ScalarString() == "style" ||
  1240  				n.MapKey.Key.Path[0].Unbox().ScalarString() == "source-arrowhead" ||
  1241  				n.MapKey.Key.Path[0].Unbox().ScalarString() == "target-arrowhead" {
  1242  				if n.MapKey.Value.Map != nil {
  1243  					deleteMapField(n.MapKey.Value.Map, field)
  1244  					if len(n.MapKey.Value.Map.Nodes) == 0 {
  1245  						deleteFromMap(m, n.MapKey)
  1246  					}
  1247  				} else if len(n.MapKey.Key.Path) == 2 && n.MapKey.Key.Path[1].Unbox().ScalarString() == field {
  1248  					deleteFromMap(m, n.MapKey)
  1249  				}
  1250  			}
  1251  		}
  1252  	}
  1253  }
  1254  
  1255  func deleteEdgeField(g *d2graph.Graph, e *d2graph.Edge, field string) error {
  1256  	for _, ref := range e.References {
  1257  		// Edge chains can't have fields
  1258  		if len(ref.MapKey.Edges) > 1 {
  1259  			continue
  1260  		}
  1261  		if ref.MapKey.Value.Map != nil {
  1262  			deleteMapField(ref.MapKey.Value.Map, field)
  1263  		} else if ref.MapKey.EdgeKey != nil && ref.MapKey.EdgeKey.Path[len(ref.MapKey.EdgeKey.Path)-1].Unbox().ScalarString() == field {
  1264  			// It's always safe to delete, since edge references must coexist with edge definition elsewhere
  1265  			deleteFromMap(ref.Scope, ref.MapKey)
  1266  		}
  1267  	}
  1268  	return nil
  1269  }
  1270  
  1271  func deleteObjField(g *d2graph.Graph, obj *d2graph.Object, field string) error {
  1272  	objK, err := d2parser.ParseKey(obj.AbsID())
  1273  	if err != nil {
  1274  		return err
  1275  	}
  1276  	objGK := d2graph.Key(objK)
  1277  	for _, ref := range obj.References {
  1278  		if ref.InEdge() {
  1279  			continue
  1280  		}
  1281  		if ref.MapKey.Value.Map != nil {
  1282  			deleteMapField(ref.MapKey.Value.Map, field)
  1283  		} else if (len(ref.Key.Path) >= 2 &&
  1284  			ref.Key.Path[len(ref.Key.Path)-1].Unbox().ScalarString() == field &&
  1285  			ref.Key.Path[len(ref.Key.Path)-2].Unbox().ScalarString() == obj.ID) ||
  1286  			(len(ref.Key.Path) >= 3 &&
  1287  				ref.Key.Path[len(ref.Key.Path)-1].Unbox().ScalarString() == field &&
  1288  				ref.Key.Path[len(ref.Key.Path)-2].Unbox().ScalarString() == "style" &&
  1289  				ref.Key.Path[len(ref.Key.Path)-3].Unbox().ScalarString() == obj.ID) {
  1290  			tmpNodes := make([]d2ast.MapNodeBox, len(ref.Scope.Nodes))
  1291  			copy(tmpNodes, ref.Scope.Nodes)
  1292  			// If I delete this, will the object still exist?
  1293  			deleteFromMap(ref.Scope, ref.MapKey)
  1294  			g2, err := recompile(g)
  1295  			if err != nil {
  1296  				return err
  1297  			}
  1298  			if _, ok := g2.Root.HasChild(objGK); !ok {
  1299  				// Nope, so can't delete it, just remove the field then
  1300  				ref.Scope.Nodes = tmpNodes
  1301  				ref.MapKey.Value = d2ast.ValueBox{}
  1302  				ref.Key.Path = ref.Key.Path[:ref.KeyPathIndex+1]
  1303  			}
  1304  
  1305  		}
  1306  	}
  1307  	return nil
  1308  }
  1309  
  1310  func deleteObject(g *d2graph.Graph, baseAST *d2ast.Map, key *d2ast.KeyPath, obj *d2graph.Object) (*d2graph.Graph, error) {
  1311  	var refEdges []*d2ast.Edge
  1312  	for _, ref := range obj.References {
  1313  		if ref.InEdge() {
  1314  			refEdges = append(refEdges, ref.MapKey.Edges[ref.MapKeyEdgeIndex])
  1315  		}
  1316  	}
  1317  
  1318  	for i := len(obj.References) - 1; i >= 0; i-- {
  1319  		ref := obj.References[i]
  1320  
  1321  		if len(ref.MapKey.Edges) == 0 {
  1322  			isSuffix := ref.KeyPathIndex == len(ref.Key.Path)-1
  1323  			if isSuffix && ref.MapKey != nil {
  1324  				ref.MapKey.Primary = d2ast.ScalarBox{}
  1325  			}
  1326  			ref.Key.Path = append(ref.Key.Path[:ref.KeyPathIndex], ref.Key.Path[ref.KeyPathIndex+1:]...)
  1327  			withoutSpecial := go2.Filter(ref.Key.Path, func(x *d2ast.StringBox) bool {
  1328  				_, isReserved := d2graph.ReservedKeywords[x.Unbox().ScalarString()]
  1329  				isSpecial := isReserved || x.Unbox().ScalarString() == "_"
  1330  				return !isSpecial
  1331  			})
  1332  			if obj.Shape.Value == d2target.ShapeSQLTable || obj.Shape.Value == d2target.ShapeClass {
  1333  				deleteFromMap(ref.Scope, ref.MapKey)
  1334  			} else if len(withoutSpecial) == 0 {
  1335  				hoistRefChildren(g, key, ref)
  1336  				deleteFromMap(ref.Scope, ref.MapKey)
  1337  			} else if ref.MapKey.Value.Unbox() == nil &&
  1338  				obj.Parent != nil &&
  1339  				isSuffix &&
  1340  				len(obj.Parent.References) > 1 {
  1341  				// Redundant key.
  1342  				deleteFromMap(ref.Scope, ref.MapKey)
  1343  			} else if ref.MapKey.Value.Map != nil && isSuffix {
  1344  				for i := 0; i < len(ref.MapKey.Value.Map.Nodes); i++ {
  1345  					n := ref.MapKey.Value.Map.Nodes[i]
  1346  					if n.MapKey != nil && n.MapKey.Key != nil {
  1347  						_, ok := d2graph.ReservedKeywords[n.MapKey.Key.Path[0].Unbox().ScalarString()]
  1348  						if ok {
  1349  							deleteFromMap(ref.MapKey.Value.Map, n.MapKey)
  1350  							i--
  1351  							continue
  1352  						}
  1353  					}
  1354  				}
  1355  			} else if isSuffix {
  1356  				ref.MapKey.Value = d2ast.ValueBox{}
  1357  			}
  1358  		} else if ref.InEdge() {
  1359  			edge := ref.MapKey.Edges[ref.MapKeyEdgeIndex]
  1360  
  1361  			if obj.Shape.Value == d2target.ShapeSQLTable || obj.Shape.Value == d2target.ShapeClass {
  1362  				if ref.MapKeyEdgeDest() {
  1363  					ensureNode(g, refEdges, ref.ScopeObj, ref.Scope, ref.MapKey, edge.Src, true)
  1364  				} else {
  1365  					ensureNode(g, refEdges, ref.ScopeObj, ref.Scope, ref.MapKey, edge.Dst, false)
  1366  				}
  1367  				deleteEdge(g, ref.Scope, ref.MapKey, ref.MapKeyEdgeIndex)
  1368  			} else if ref.KeyPathIndex == len(ref.Key.Path)-1 {
  1369  				if ref.MapKeyEdgeDest() {
  1370  					ensureNode(g, refEdges, ref.ScopeObj, ref.Scope, ref.MapKey, edge.Src, true)
  1371  				} else {
  1372  					ensureNode(g, refEdges, ref.ScopeObj, ref.Scope, ref.MapKey, edge.Dst, false)
  1373  				}
  1374  				deleteEdge(g, ref.Scope, ref.MapKey, ref.MapKeyEdgeIndex)
  1375  			} else {
  1376  				ref.Key.Path = append(ref.Key.Path[:ref.KeyPathIndex], ref.Key.Path[ref.KeyPathIndex+1:]...)
  1377  
  1378  				// Skip visiting the same middle key in an edge chain
  1379  				if !ref.MapKeyEdgeDest() && i > 0 {
  1380  					nextRef := obj.References[i-1]
  1381  					if nextRef.InEdge() && nextRef.MapKey == ref.MapKey {
  1382  						i--
  1383  					}
  1384  				}
  1385  			}
  1386  		} else {
  1387  			// MapKey.Key with edge.
  1388  			ref.Key.Path = append(ref.Key.Path[:ref.KeyPathIndex], ref.Key.Path[ref.KeyPathIndex+1:]...)
  1389  			if len(ref.Key.Path) == 0 {
  1390  				ref.MapKey.Key = nil
  1391  			}
  1392  		}
  1393  	}
  1394  
  1395  	return g, nil
  1396  }
  1397  
  1398  func findNearestParentScope(g *d2graph.Graph, k *d2ast.KeyPath) (prefix *d2ast.KeyPath, _ *d2ast.Map) {
  1399  	for i := 1; i < len(k.Path); i++ {
  1400  		scopeKey := cloneKey(k)
  1401  		scopeKey.Path = scopeKey.Path[:len(k.Path)-i]
  1402  		obj, ok := g.Root.HasChild(d2graph.Key(scopeKey))
  1403  		if ok && obj.Map != nil {
  1404  			prefix := cloneKey(k)
  1405  			prefix.Path = prefix.Path[len(k.Path)-i:]
  1406  			return prefix, obj.Map
  1407  		}
  1408  	}
  1409  	return k, g.AST
  1410  }
  1411  
  1412  func deleteEdge(g *d2graph.Graph, scope *d2ast.Map, mk *d2ast.Key, i int) {
  1413  	edgesAfter := mk.Edges[i+1:]
  1414  	mk.Edges = mk.Edges[:i]
  1415  
  1416  	for _, obj := range g.Objects {
  1417  		for j := range obj.References {
  1418  			ref := obj.References[j]
  1419  			if ref.InEdge() {
  1420  				if ref.MapKey == mk && ref.MapKeyEdgeIndex >= i {
  1421  					obj.References[j].MapKeyEdgeIndex -= i
  1422  				}
  1423  			}
  1424  		}
  1425  	}
  1426  
  1427  	if len(edgesAfter) > 0 {
  1428  		tmp := *mk
  1429  		mk2 := &tmp
  1430  		mk2.Edges = edgesAfter
  1431  		scope.InsertAfter(mk, mk2)
  1432  	}
  1433  	if len(mk.Edges) == 0 {
  1434  		deleteFromMap(scope, mk)
  1435  	}
  1436  }
  1437  
  1438  // ensureNode ensures that `k` exists in `scope` if `excludedEdges` were removed
  1439  func ensureNode(g *d2graph.Graph, excludedEdges []*d2ast.Edge, scopeObj *d2graph.Object, scope *d2ast.Map, cursor *d2ast.Key, k *d2ast.KeyPath, before bool) {
  1440  	if k == nil || len(k.Path) == 0 {
  1441  		return
  1442  	}
  1443  	if cursor.Key != nil && len(cursor.Key.Path) > 0 {
  1444  		k = cloneKey(k)
  1445  		k.Path = append(cursor.Key.Path, k.Path...)
  1446  	}
  1447  
  1448  	obj, ok := scopeObj.HasChild(d2graph.Key(k))
  1449  	if ok {
  1450  		// If this key only exists as part of excludedEdges (edges that'll be deleted), we need to make a new one
  1451  		hasPersistingRef := false
  1452  		for _, ref := range obj.References {
  1453  			if !ref.InEdge() {
  1454  				hasPersistingRef = true
  1455  				break
  1456  			}
  1457  			if len(ref.MapKey.Edges) == 0 {
  1458  				continue
  1459  			}
  1460  			if !go2.Contains(excludedEdges, ref.MapKey.Edges[ref.MapKeyEdgeIndex]) {
  1461  				hasPersistingRef = true
  1462  				break
  1463  			}
  1464  		}
  1465  		if hasPersistingRef {
  1466  			return
  1467  		}
  1468  	}
  1469  	mk := &d2ast.Key{
  1470  		Key: k,
  1471  	}
  1472  
  1473  	for _, n := range scope.Nodes {
  1474  		if n.MapKey != nil && n.MapKey.D2OracleEquals(mk) {
  1475  			return
  1476  		}
  1477  	}
  1478  
  1479  	if before {
  1480  		scope.InsertBefore(cursor, mk)
  1481  	} else {
  1482  		scope.InsertAfter(cursor, mk)
  1483  	}
  1484  }
  1485  
  1486  func Rename(g *d2graph.Graph, boardPath []string, key, newName string) (_ *d2graph.Graph, newKey string, err error) {
  1487  	defer xdefer.Errorf(&err, "failed to rename %#v to %#v", key, newName)
  1488  
  1489  	mk, err := d2parser.ParseMapKey(key)
  1490  	if err != nil {
  1491  		return nil, "", err
  1492  	}
  1493  
  1494  	boardG := g
  1495  
  1496  	if len(boardPath) > 0 {
  1497  		boardG = GetBoardGraph(g, boardPath)
  1498  		if boardG == nil {
  1499  			return nil, "", fmt.Errorf("board %v not found", boardPath)
  1500  		}
  1501  	}
  1502  
  1503  	if len(mk.Edges) > 0 && mk.EdgeKey == nil {
  1504  		// TODO: Not a fan of this dual interpretation depending on mk.Edges.
  1505  		// Maybe we remove Rename and just have Move.
  1506  		mk2, err := d2parser.ParseMapKey(newName)
  1507  		if err != nil {
  1508  			return nil, "", err
  1509  		}
  1510  
  1511  		mk2.Key = mk.Key
  1512  		mk = mk2
  1513  	} else {
  1514  		_, ok := d2graph.ReservedKeywords[newName]
  1515  		if ok {
  1516  			return nil, "", fmt.Errorf("cannot rename to reserved keyword: %#v", newName)
  1517  		}
  1518  		if mk.Key != nil {
  1519  			obj, ok := boardG.Root.HasChild(d2graph.Key(mk.Key))
  1520  			if !ok {
  1521  				return nil, "", fmt.Errorf("key does not exist")
  1522  			}
  1523  			// If attempt to name something "x", but "x" already exists, rename it "x 2" instead
  1524  			generatedName, _, err := generateUniqueKey(boardG, newName, obj, nil)
  1525  			if err == nil {
  1526  				newName = generatedName
  1527  			}
  1528  		}
  1529  		// TODO: Handle mk.EdgeKey
  1530  		mk.Key.Path[len(mk.Key.Path)-1] = d2ast.MakeValueBox(d2ast.RawString(newName, true)).StringBox()
  1531  	}
  1532  
  1533  	g, err = move(g, boardPath, key, d2format.Format(mk), false)
  1534  	return g, newName, err
  1535  }
  1536  
  1537  func trimReservedSuffix(path []*d2ast.StringBox) []*d2ast.StringBox {
  1538  	for i, p := range path {
  1539  		if _, ok := d2graph.ReservedKeywords[p.Unbox().ScalarString()]; ok {
  1540  			return path[:i]
  1541  		}
  1542  	}
  1543  	return path
  1544  }
  1545  
  1546  // Does not handle edge keys, on account of edge keys can only be reserved, e.g. (a->b).style.color: red
  1547  func Move(g *d2graph.Graph, boardPath []string, key, newKey string, includeDescendants bool) (_ *d2graph.Graph, err error) {
  1548  	defer xdefer.Errorf(&err, "failed to move: %#v to %#v", key, newKey)
  1549  	return move(g, boardPath, key, newKey, includeDescendants)
  1550  }
  1551  
  1552  func move(g *d2graph.Graph, boardPath []string, key, newKey string, includeDescendants bool) (*d2graph.Graph, error) {
  1553  	if key == newKey {
  1554  		return g, nil
  1555  	}
  1556  
  1557  	boardG := g
  1558  	baseAST := g.AST
  1559  
  1560  	if len(boardPath) > 0 {
  1561  		// When compiling a nested board, we can read from boardG but only write to baseBoardG
  1562  		boardG = GetBoardGraph(g, boardPath)
  1563  		if boardG == nil {
  1564  			return nil, fmt.Errorf("board %v not found", boardPath)
  1565  		}
  1566  		// TODO beter name
  1567  		baseAST = boardG.BaseAST
  1568  	}
  1569  
  1570  	newKey, _, err := generateUniqueKey(boardG, newKey, nil, nil)
  1571  	if err != nil {
  1572  		return nil, err
  1573  	}
  1574  
  1575  	mk, err := d2parser.ParseMapKey(key)
  1576  	if err != nil {
  1577  		return nil, err
  1578  	}
  1579  
  1580  	mk2, err := d2parser.ParseMapKey(newKey)
  1581  	if err != nil {
  1582  		return nil, err
  1583  	}
  1584  	edgeTrimCommon(mk)
  1585  	edgeTrimCommon(mk2)
  1586  
  1587  	if len(mk.Edges) > 0 && mk.EdgeKey == nil {
  1588  		if d2format.Format(mk.Key) != d2format.Format(mk2.Key) {
  1589  			// TODO just prevent moving edges at all
  1590  			return nil, errors.New("moving across scopes isn't supported for edges")
  1591  		}
  1592  		obj := g.Root
  1593  		if mk.Key != nil {
  1594  			var ok bool
  1595  			obj, ok = g.Root.HasChild(d2graph.Key(mk.Key))
  1596  			if !ok {
  1597  				return nil, fmt.Errorf("edge referenced by from does not exist")
  1598  			}
  1599  		}
  1600  		e, ok := obj.HasEdge(mk)
  1601  		if !ok {
  1602  			return nil, fmt.Errorf("edge referenced by to does not exist")
  1603  		}
  1604  		_, ok = obj.HasEdge(mk2)
  1605  		if ok {
  1606  			return nil, fmt.Errorf("to edge already exists")
  1607  		}
  1608  
  1609  		for i := len(e.References) - 1; i >= 0; i-- {
  1610  			ref := e.References[i]
  1611  			ref.MapKey.Edges[ref.MapKeyEdgeIndex].SrcArrow = mk2.Edges[0].SrcArrow
  1612  			ref.MapKey.Edges[ref.MapKeyEdgeIndex].DstArrow = mk2.Edges[0].DstArrow
  1613  		}
  1614  		return recompile(g)
  1615  	}
  1616  
  1617  	prevG, _ := recompile(boardG)
  1618  
  1619  	ak := d2graph.Key(mk.Key)
  1620  	ak2 := d2graph.Key(mk2.Key)
  1621  
  1622  	isCrossScope := strings.Join(ak[:len(ak)-1], ".") != strings.Join(ak2[:len(ak2)-1], ".")
  1623  
  1624  	if isCrossScope && !includeDescendants {
  1625  		boardG, err = renameConflictsToParent(boardG, mk.Key)
  1626  		if err != nil {
  1627  			return nil, err
  1628  		}
  1629  	}
  1630  
  1631  	obj, ok := boardG.Root.HasChild(ak)
  1632  	if !ok {
  1633  		return nil, fmt.Errorf("key referenced by from does not exist")
  1634  	}
  1635  
  1636  	if len(boardPath) > 0 {
  1637  		writeableRefs := getWriteableRefs(obj, baseAST)
  1638  		if len(writeableRefs) != len(obj.References) {
  1639  			return nil, OutsideScopeError{}
  1640  		}
  1641  	}
  1642  
  1643  	toParent := boardG.Root
  1644  	if isCrossScope && len(ak2) > 1 {
  1645  		toParent, ok = boardG.Root.HasChild(ak2[:len(ak2)-1])
  1646  		if !ok {
  1647  			return nil, fmt.Errorf("key referenced by to parent does not exist")
  1648  		}
  1649  	}
  1650  
  1651  	// Cross-scope move:
  1652  	// 1. Ensure parent node exists as a Key
  1653  	// 2. Ensure parent node Key has a map to accept moved node
  1654  	// 3. Rename
  1655  	// 4. Update all Key references
  1656  	// 5. Update all Edge references
  1657  
  1658  	// 1. Ensure parent node exists as a Key
  1659  	// The toParent may only exist as an implicit, edge-created node
  1660  	//
  1661  	// For example, d.e here
  1662  	// a.b.c -> d.e.f
  1663  	// MOVE(a.b, d.e.q)
  1664  	// We can't open d.e as a map, so we need to create a new key
  1665  
  1666  	// If the key targeted exists only as implicit edge creation, e.g. the b in `a.b -> ...`,
  1667  	// then we'll be able to move it anywhere by changing that key, e.g. `_._.c.x -> ...`
  1668  	// Otherwise, we'll need to make sure the parent exists as a map to move into
  1669  	needsLandingMap := false
  1670  	if isCrossScope {
  1671  		for _, ref := range obj.References {
  1672  			if !ref.InEdge() {
  1673  				needsLandingMap = true
  1674  				break
  1675  			}
  1676  			if ref.KeyPathIndex != len(ref.Key.Path)-1 {
  1677  				needsLandingMap = true
  1678  				break
  1679  			}
  1680  		}
  1681  	}
  1682  	if isCrossScope && len(ak2) > 1 && needsLandingMap {
  1683  		parentExistsAsKey := false
  1684  		for _, ref := range toParent.References {
  1685  			if len(ref.MapKey.Edges) == 0 {
  1686  				parentExistsAsKey = true
  1687  				break
  1688  			}
  1689  		}
  1690  		if !parentExistsAsKey {
  1691  			// Choose the most nested edge as cursor for this new node
  1692  			var mostNestedRef d2graph.Reference
  1693  			for _, ref := range toParent.References {
  1694  				if mostNestedRef == (d2graph.Reference{}) || len(ref.ScopeObj.AbsIDArray()) > len(mostNestedRef.ScopeObj.AbsIDArray()) {
  1695  					mostNestedRef = ref
  1696  				}
  1697  			}
  1698  			detachedMK := &d2ast.Key{
  1699  				Key: cloneKey(mostNestedRef.MapKey.Key),
  1700  			}
  1701  			detachedMK.Key.Path = mostNestedRef.Key.Path[:mostNestedRef.KeyPathIndex+1]
  1702  			detachedMK.Range = d2ast.MakeRange(",1:0:0-1:0:0")
  1703  			mostNestedRef.Scope.InsertAfter(mostNestedRef.MapKey, detachedMK)
  1704  
  1705  			mostNestedRef.ScopeObj.AppendReferences(d2graph.Key(detachedMK.Key), d2graph.Reference{
  1706  				Key:    detachedMK.Key,
  1707  				MapKey: detachedMK,
  1708  				Scope:  mostNestedRef.Scope,
  1709  			}, mostNestedRef.ScopeObj)
  1710  		}
  1711  	}
  1712  
  1713  	// 2. Ensure parent node Key has a map to accept moved node.
  1714  	// This map will be what MOVE will append the new key to
  1715  	toScope := boardG.AST
  1716  	if isCrossScope && len(ak2) > 1 && needsLandingMap {
  1717  		mostNestedParentRefs := getMostNestedRefs(toParent)
  1718  		mapExists := false
  1719  		for _, ref := range mostNestedParentRefs {
  1720  			if ref.KeyPathIndex == len(ref.Key.Path)-1 && ref.MapKey.Value.Map != nil {
  1721  				toScope = ref.MapKey.Value.Map
  1722  				mapExists = true
  1723  				break
  1724  			}
  1725  		}
  1726  
  1727  		if !mapExists {
  1728  			toScope = &d2ast.Map{
  1729  				Range: d2ast.MakeRange(",1:0:0-1:0:0"),
  1730  			}
  1731  
  1732  			ref := mostNestedParentRefs[len(mostNestedParentRefs)-1]
  1733  			// Parent node key exists as part of a flat key, need to split up
  1734  			if ref.KeyPathIndex < len(ref.Key.Path)-1 {
  1735  				detachedMK := &d2ast.Key{
  1736  					Key: cloneKey(ref.MapKey.Key),
  1737  				}
  1738  				detachedMK.Value = ref.MapKey.Value
  1739  				detachedMK.Key.Path = ref.Key.Path[ref.KeyPathIndex+1:]
  1740  				detachedMK.Range = d2ast.MakeRange(",1:0:0-1:0:0")
  1741  
  1742  				ref.Key.Path = ref.Key.Path[:ref.KeyPathIndex+1]
  1743  				appendUniqueMapKey(toScope, detachedMK)
  1744  			} else {
  1745  				ref.MapKey.Primary = ref.MapKey.Value.ScalarBox()
  1746  			}
  1747  			ref.MapKey.Value = d2ast.MakeValueBox(toScope)
  1748  		}
  1749  	}
  1750  	mostNestedRefs := getMostNestedRefs(obj)
  1751  	for _, ref := range obj.References {
  1752  		isExplicit := ref.KeyPathIndex == len(trimReservedSuffix(ref.Key.Path))-1 && ref.MapKey.Value.Map == nil
  1753  
  1754  		// 3. Rename
  1755  		if ak[len(ak)-1] != ak2[len(ak2)-1] {
  1756  			ref.Key.Path[ref.KeyPathIndex] = d2ast.MakeValueBox(d2ast.RawString(
  1757  				mk2.Key.Path[len(mk2.Key.Path)-1].Unbox().ScalarString(),
  1758  				true,
  1759  			)).StringBox()
  1760  		}
  1761  
  1762  		// 4. Update all Key references
  1763  		if len(ref.MapKey.Edges) != 0 {
  1764  			continue
  1765  		}
  1766  
  1767  		firstNonUnderscoreIndex := 0
  1768  		ida := d2graph.Key(ref.Key)
  1769  		for i, id := range ida {
  1770  			if id != "_" {
  1771  				firstNonUnderscoreIndex = i
  1772  				break
  1773  			}
  1774  		}
  1775  		resolvedObj, resolvedIDA, err := d2graph.ResolveUnderscoreKey(ida, ref.ScopeObj)
  1776  		if err != nil {
  1777  			return nil, err
  1778  		}
  1779  		if resolvedObj != obj {
  1780  			ida = resolvedIDA
  1781  		}
  1782  		// e.g. "a.b.shape: circle"
  1783  		_, endsWithReserved := d2graph.ReservedKeywords[ida[len(ida)-1]]
  1784  		ida = go2.Filter(ida, func(x string) bool {
  1785  			_, ok := d2graph.ReservedKeywords[x]
  1786  			return !ok
  1787  		})
  1788  
  1789  		// There are 3 cases of what we want to do with Key references in cross scope
  1790  		// 1. Transplant. Remove from its current scope, plop it into new scope
  1791  		// -- The ref key is the key being moved
  1792  		// 2. Split. One node remains, while another gets added to new scope
  1793  		// -- The ref key is a flat map with more than just the key being moved
  1794  		// 3. Extend.
  1795  		// -- The key is moving from its current scope into a more nested scope
  1796  		// 4. Slice.
  1797  		// -- The key is moving from its current scope out to a less nested scope
  1798  		if isCrossScope {
  1799  			if (!includeDescendants && len(ida) == 1) || (includeDescendants && ref.KeyPathIndex == firstNonUnderscoreIndex) {
  1800  				// 1. Transplant
  1801  				absKey, err := d2parser.ParseKey(ref.ScopeObj.AbsID())
  1802  				if err != nil {
  1803  					absKey = &d2ast.KeyPath{}
  1804  				}
  1805  				absKey.Path = append(absKey.Path, ref.Key.Path...)
  1806  				if !includeDescendants {
  1807  					hoistRefChildren(boardG, absKey, ref)
  1808  				}
  1809  				deleteFromMap(ref.Scope, ref.MapKey)
  1810  				detachedMK := &d2ast.Key{Primary: ref.MapKey.Primary, Key: cloneKey(ref.MapKey.Key)}
  1811  				detachedMK.Key.Path = go2.Filter(detachedMK.Key.Path, func(x *d2ast.StringBox) bool {
  1812  					return x.Unbox().ScalarString() != "_"
  1813  				})
  1814  				detachedMK.Value = ref.MapKey.Value
  1815  				if ref.MapKey != nil && ref.MapKey.Value.Map != nil {
  1816  					// Without including descendants, just copy over the reserved
  1817  					if !includeDescendants {
  1818  						detachedMK.Value.Map = &d2ast.Map{
  1819  							Range: ref.MapKey.Value.Map.Range,
  1820  						}
  1821  						for _, n := range ref.MapKey.Value.Map.Nodes {
  1822  							if n.MapKey == nil {
  1823  								continue
  1824  							}
  1825  							if n.MapKey.Key != nil {
  1826  								_, ok := d2graph.ReservedKeywords[n.MapKey.Key.Path[0].Unbox().ScalarString()]
  1827  								if ok {
  1828  									detachedMK.Value.Map.Nodes = append(detachedMK.Value.Map.Nodes, n)
  1829  								}
  1830  							}
  1831  						}
  1832  						if len(detachedMK.Value.Map.Nodes) == 0 {
  1833  							detachedMK.Value.Map = nil
  1834  						}
  1835  					} else {
  1836  						// Usually copy everything as is when including descendants
  1837  						// The exception is underscored keys, which need to be updated
  1838  						for _, n := range ref.MapKey.Value.Map.Nodes {
  1839  							if n.MapKey == nil {
  1840  								continue
  1841  							}
  1842  							if n.MapKey.Key != nil {
  1843  								if n.MapKey.Key.Path[0].Unbox().ScalarString() == "_" {
  1844  									resolvedParent, resolvedScopeKey, err := d2graph.ResolveUnderscoreKey(d2graph.Key(n.MapKey.Key), obj)
  1845  									if err != nil {
  1846  										return nil, err
  1847  									}
  1848  
  1849  									newPath, err := pathFromScopeKey(boardG, &d2ast.Key{Key: d2ast.MakeKeyPath(append(resolvedParent.AbsIDArray(), resolvedScopeKey...))}, ak2)
  1850  									if err != nil {
  1851  										return nil, err
  1852  									}
  1853  									n.MapKey.Key.Path = newPath
  1854  								}
  1855  							}
  1856  							for _, e := range n.MapKey.Edges {
  1857  								if e.Src.Path[0].Unbox().ScalarString() == "_" {
  1858  									resolvedParent, resolvedScopeKey, err := d2graph.ResolveUnderscoreKey(d2graph.Key(e.Src), obj)
  1859  									if err != nil {
  1860  										return nil, err
  1861  									}
  1862  
  1863  									newPath, err := pathFromScopeKey(boardG, &d2ast.Key{Key: d2ast.MakeKeyPath(append(resolvedParent.AbsIDArray(), resolvedScopeKey...))}, ak2)
  1864  									if err != nil {
  1865  										return nil, err
  1866  									}
  1867  									e.Src.Path = newPath
  1868  								}
  1869  								if e.Dst.Path[0].Unbox().ScalarString() == "_" {
  1870  									resolvedParent, resolvedScopeKey, err := d2graph.ResolveUnderscoreKey(d2graph.Key(e.Dst), obj)
  1871  									if err != nil {
  1872  										return nil, err
  1873  									}
  1874  
  1875  									newPath, err := pathFromScopeKey(boardG, &d2ast.Key{Key: d2ast.MakeKeyPath(append(resolvedParent.AbsIDArray(), resolvedScopeKey...))}, ak2)
  1876  									if err != nil {
  1877  										return nil, err
  1878  									}
  1879  									e.Dst.Path = newPath
  1880  								}
  1881  							}
  1882  						}
  1883  					}
  1884  				}
  1885  				appendUniqueMapKey(toScope, detachedMK)
  1886  			} else if len(ida) > 1 && (endsWithReserved || !isExplicit || go2.Contains(mostNestedRefs, ref)) {
  1887  				// 2. Split
  1888  				detachedMK := &d2ast.Key{Key: cloneKey(ref.MapKey.Key)}
  1889  				if includeDescendants {
  1890  					detachedMK.Key.Path = append([]*d2ast.StringBox{}, ref.Key.Path[ref.KeyPathIndex:]...)
  1891  				} else {
  1892  					detachedMK.Key.Path = []*d2ast.StringBox{ref.Key.Path[ref.KeyPathIndex]}
  1893  				}
  1894  				if includeDescendants {
  1895  					detachedMK.Value = ref.MapKey.Value
  1896  					ref.MapKey.Value = d2ast.ValueBox{}
  1897  				} else if ref.KeyPathIndex == len(filterReservedPath(ref.Key.Path))-1 {
  1898  					withReserved, withoutReserved := filterReserved(ref.MapKey.Value)
  1899  					detachedMK.Value = withReserved
  1900  					ref.MapKey.Value = withoutReserved
  1901  					detachedMK.Key.Path = append([]*d2ast.StringBox{}, ref.Key.Path[ref.KeyPathIndex:]...)
  1902  					ref.Key.Path = ref.Key.Path[:ref.KeyPathIndex+1]
  1903  				}
  1904  				if includeDescendants {
  1905  					ref.Key.Path = ref.Key.Path[:ref.KeyPathIndex]
  1906  				} else {
  1907  					ref.Key.Path = append(ref.Key.Path[:ref.KeyPathIndex], ref.Key.Path[ref.KeyPathIndex+1:]...)
  1908  				}
  1909  				appendUniqueMapKey(toScope, detachedMK)
  1910  			} else if len(getCommonPath(ak, ak2)) > 0 {
  1911  				// 3. Extend
  1912  				// This case does not make sense for includeDescendants
  1913  				newKeyPath := ref.Key.Path[:ref.KeyPathIndex]
  1914  				newKeyPath = append(newKeyPath, mk2.Key.Path[len(getCommonPath(ak, ak2)):]...)
  1915  				ref.Key.Path = append(newKeyPath, ref.Key.Path[ref.KeyPathIndex+1:]...)
  1916  			} else {
  1917  				// 4. Slice
  1918  				scopePath := ref.ScopeObj.AbsIDArray()
  1919  				if len(getCommonPath(scopePath, ak2)) != len(scopePath) {
  1920  					deleteFromMap(ref.Scope, ref.MapKey)
  1921  				} else {
  1922  					ref.Key.Path = ref.Key.Path[ref.KeyPathIndex:]
  1923  					exists := false
  1924  					for _, n := range toScope.Nodes {
  1925  						if n.MapKey != nil && n.MapKey != ref.MapKey && n.MapKey.D2OracleEquals(ref.MapKey) {
  1926  							exists = true
  1927  						}
  1928  					}
  1929  					if exists {
  1930  						deleteFromMap(ref.Scope, ref.MapKey)
  1931  					}
  1932  				}
  1933  			}
  1934  		}
  1935  	}
  1936  	var refEdges []*d2ast.Edge
  1937  	for _, ref := range obj.References {
  1938  		if ref.InEdge() {
  1939  			refEdges = append(refEdges, ref.MapKey.Edges[ref.MapKeyEdgeIndex])
  1940  		}
  1941  	}
  1942  	for i := 0; i < len(obj.References); i++ {
  1943  		if !isCrossScope {
  1944  			break
  1945  		}
  1946  
  1947  		ref := obj.References[i]
  1948  		// 5. Update all Edge references
  1949  		if len(ref.MapKey.Edges) == 0 {
  1950  			continue
  1951  		}
  1952  
  1953  		if i > 0 && ref.Key == obj.References[i-1].Key {
  1954  			continue
  1955  		}
  1956  
  1957  		firstNonUnderscoreIndex := 0
  1958  		ida := d2graph.Key(ref.Key)
  1959  		for i, id := range ida {
  1960  			if id != "_" {
  1961  				firstNonUnderscoreIndex = i
  1962  				break
  1963  			}
  1964  		}
  1965  
  1966  		if ref.KeyPathIndex != len(ref.Key.Path)-1 {
  1967  			// When moving a node out of an edge, e.g. the `b` out of `a.b.c -> ...`,
  1968  			// The edge needs to continue targeting the same thing (c)
  1969  			// Split
  1970  			detachedMK := &d2ast.Key{
  1971  				Key: cloneKey(ref.Key),
  1972  			}
  1973  			oldPath, err := pathFromScopeObj(boardG, mk, ref.ScopeObj)
  1974  			if err != nil {
  1975  				return nil, err
  1976  			}
  1977  			newPath, err := pathFromScopeObj(boardG, mk2, ref.ScopeObj)
  1978  			if err != nil {
  1979  				return nil, err
  1980  			}
  1981  			if includeDescendants {
  1982  				// When including descendants, the only thing that gets dropped, if any, is the uncommon leading path of new key and key
  1983  				// E.g. when moving `a.b` to `x.b` with edge ref key of `a.b.c`, then changing it to `x.b.c` will drop `a`
  1984  				diff := len(oldPath) - len(newPath)
  1985  				// Only need to check uncommon path if the lengths are the same
  1986  				if diff == 0 {
  1987  					diff = len(getUncommonPath(d2graph.Key(&d2ast.KeyPath{Path: oldPath}), d2graph.Key(&d2ast.KeyPath{Path: newPath})))
  1988  				}
  1989  				// If the old key is longer than the new key, we already know all the diff would be dropped
  1990  				if diff > 0 && ref.KeyPathIndex != firstNonUnderscoreIndex {
  1991  					detachedMK.Key.Path = append([]*d2ast.StringBox{}, ref.Key.Path[ref.KeyPathIndex-diff:ref.KeyPathIndex]...)
  1992  					appendUniqueMapKey(ref.Scope, detachedMK)
  1993  				}
  1994  			} else {
  1995  				detachedMK.Key.Path = []*d2ast.StringBox{ref.Key.Path[ref.KeyPathIndex]}
  1996  				appendUniqueMapKey(toScope, detachedMK)
  1997  			}
  1998  
  1999  			if includeDescendants {
  2000  				ref.Key.Path = append(ref.Key.Path[:ref.KeyPathIndex-len(oldPath)+1], append(newPath, ref.Key.Path[ref.KeyPathIndex+1:]...)...)
  2001  			} else {
  2002  				ref.Key.Path = append(ref.Key.Path[:ref.KeyPathIndex], ref.Key.Path[ref.KeyPathIndex+1:]...)
  2003  			}
  2004  		} else {
  2005  			// When moving a node connected to an edge, we have to ensure parents continue to exist
  2006  			// e.g. the `c` out of `a.b.c -> ...`
  2007  			// `a.b` needs to exist
  2008  			newPath, err := pathFromScopeObj(boardG, mk2, ref.ScopeObj)
  2009  			if err != nil {
  2010  				return nil, err
  2011  			}
  2012  			if len(go2.Filter(ref.Key.Path, func(x *d2ast.StringBox) bool { return x.Unbox().ScalarString() != "_" })) > 1 {
  2013  				detachedK := cloneKey(ref.Key)
  2014  				detachedK.Path = detachedK.Path[:len(detachedK.Path)-1]
  2015  				ensureNode(boardG, refEdges, ref.ScopeObj, ref.Scope, ref.MapKey, detachedK, false)
  2016  			}
  2017  
  2018  			if includeDescendants {
  2019  				ref.Key.Path = append(newPath, ref.Key.Path[go2.Min(len(ref.Key.Path), ref.KeyPathIndex+len(newPath)):]...)
  2020  			} else {
  2021  				ref.Key.Path = newPath
  2022  			}
  2023  		}
  2024  	}
  2025  
  2026  	if err := updateNear(prevG, boardG, &key, &newKey, includeDescendants); err != nil {
  2027  		return nil, err
  2028  	}
  2029  
  2030  	if len(boardPath) > 0 {
  2031  		replaced := ReplaceBoardNode(g.AST, baseAST, boardPath)
  2032  		if !replaced {
  2033  			return nil, fmt.Errorf("board %v AST not found", boardPath)
  2034  		}
  2035  		return recompile(g)
  2036  	}
  2037  
  2038  	return recompile(boardG)
  2039  }
  2040  
  2041  // filterReserved takes a Value and splits it into 2
  2042  // 1. Value with reserved keywords
  2043  // 2. Without reserved keywords
  2044  // Maintains structure, so if reserved keywords were part of map, the output will keep them in a map
  2045  func filterReserved(value d2ast.ValueBox) (with, without d2ast.ValueBox) {
  2046  	with, without = d2ast.MakeValueBox(value.Unbox()), d2ast.ValueBox{}
  2047  
  2048  	if value.Map != nil {
  2049  		var forWith []d2ast.MapNodeBox
  2050  		var forWithout []d2ast.MapNodeBox
  2051  
  2052  		// assume comments are above what they describe
  2053  		// going down the map line by line, we batch here as we encounter, and flush to either forWith or forWithout, whichever hits first
  2054  		var commentBatch []d2ast.MapNodeBox
  2055  		flushComments := func(to *[]d2ast.MapNodeBox) {
  2056  			*to = append(*to, commentBatch...)
  2057  			commentBatch = nil
  2058  		}
  2059  
  2060  		for _, n := range value.Map.Nodes {
  2061  			if n.MapKey == nil {
  2062  				if n.Comment != nil || n.BlockComment != nil {
  2063  					commentBatch = append(commentBatch, n)
  2064  				}
  2065  				continue
  2066  			}
  2067  			if n.MapKey.Key == nil || (len(n.MapKey.Key.Path) > 1) {
  2068  				flushComments(&forWithout)
  2069  				forWithout = append(forWithout, n)
  2070  				continue
  2071  			}
  2072  			_, ok := d2graph.ReservedKeywords[n.MapKey.Key.Path[0].Unbox().ScalarString()]
  2073  			if !ok {
  2074  				flushComments(&forWithout)
  2075  				forWithout = append(forWithout, n)
  2076  				continue
  2077  			}
  2078  			flushComments(&forWith)
  2079  			forWith = append(forWith, n)
  2080  		}
  2081  
  2082  		if len(forWith) > 0 {
  2083  			if with.Map == nil {
  2084  				with.Map = &d2ast.Map{
  2085  					Range: d2ast.MakeRange(",1:0:0-1:0:0"),
  2086  				}
  2087  			}
  2088  			with.Map.Nodes = forWith
  2089  		} else {
  2090  			with.Map = nil
  2091  		}
  2092  		if len(forWithout) > 0 {
  2093  			if without.Map == nil {
  2094  				without.Map = &d2ast.Map{
  2095  					Range: value.Map.Range,
  2096  				}
  2097  			}
  2098  			without.Map.Nodes = forWithout
  2099  		} else {
  2100  			without.Map = nil
  2101  		}
  2102  	}
  2103  
  2104  	return
  2105  }
  2106  
  2107  // updateNear updates all the Near fields
  2108  // prevG is the graph before the update (i.e. deletion, rename, move)
  2109  func updateNear(prevG, g *d2graph.Graph, from, to *string, includeDescendants bool) error {
  2110  	mk, _ := d2parser.ParseMapKey(*from)
  2111  	if len(mk.Edges) > 0 {
  2112  		return nil
  2113  	}
  2114  	if mk.Key == nil {
  2115  		return nil
  2116  	}
  2117  	if len(mk.Key.Path) == 0 {
  2118  		return nil
  2119  	}
  2120  
  2121  	// TODO get rid of repetition
  2122  
  2123  	// Update all the `near` keys that are one level nested
  2124  	// x: {
  2125  	//   near: z
  2126  	// }
  2127  	for _, obj := range g.Objects {
  2128  		if obj.Map == nil {
  2129  			continue
  2130  		}
  2131  		for _, n := range obj.Map.Nodes {
  2132  			if n.MapKey == nil {
  2133  				continue
  2134  			}
  2135  			if n.MapKey.Key == nil {
  2136  				continue
  2137  			}
  2138  			if len(n.MapKey.Key.Path) == 0 {
  2139  				continue
  2140  			}
  2141  			if n.MapKey.Key.Path[len(n.MapKey.Key.Path)-1].Unbox().ScalarString() == "near" {
  2142  				k := n.MapKey.Value.ScalarBox().Unbox().ScalarString()
  2143  				if strings.EqualFold(k, *from) && to == nil {
  2144  					deleteFromMap(obj.Map, n.MapKey)
  2145  				} else {
  2146  					valueMK, err := d2parser.ParseMapKey(k)
  2147  					if err != nil {
  2148  						return err
  2149  					}
  2150  					tmpG, _ := recompile(prevG)
  2151  					appendMapKey(tmpG.AST, valueMK)
  2152  					if to == nil {
  2153  						deltas, err := DeleteIDDeltas(tmpG, nil, *from)
  2154  						if err != nil {
  2155  							return err
  2156  						}
  2157  						if v, ok := deltas[k]; ok {
  2158  							n.MapKey.Value = d2ast.MakeValueBox(d2ast.RawString(v, false))
  2159  						}
  2160  					} else {
  2161  						deltas, err := MoveIDDeltas(tmpG, *from, *to, includeDescendants)
  2162  						if err != nil {
  2163  							return err
  2164  						}
  2165  						if v, ok := deltas[k]; ok {
  2166  							n.MapKey.Value = d2ast.MakeValueBox(d2ast.RawString(v, false))
  2167  						}
  2168  					}
  2169  				}
  2170  			}
  2171  		}
  2172  	}
  2173  
  2174  	// Update all the `near` keys that are flat (x.near: z)
  2175  	for _, obj := range g.Objects {
  2176  		for _, ref := range obj.References {
  2177  			if ref.MapKey == nil {
  2178  				continue
  2179  			}
  2180  			if ref.MapKey.Key == nil {
  2181  				continue
  2182  			}
  2183  			if len(ref.MapKey.Key.Path) == 0 {
  2184  				continue
  2185  			}
  2186  			if ref.MapKey.Key.Path[len(ref.MapKey.Key.Path)-1].Unbox().ScalarString() == "near" {
  2187  				k := ref.MapKey.Value.ScalarBox().Unbox().ScalarString()
  2188  				if strings.EqualFold(k, *from) && to == nil {
  2189  					deleteFromMap(obj.Map, ref.MapKey)
  2190  				} else {
  2191  					valueMK, err := d2parser.ParseMapKey(k)
  2192  					if err != nil {
  2193  						return err
  2194  					}
  2195  					tmpG, _ := recompile(prevG)
  2196  					appendMapKey(tmpG.AST, valueMK)
  2197  					if to == nil {
  2198  						deltas, err := DeleteIDDeltas(tmpG, nil, *from)
  2199  						if err != nil {
  2200  							return err
  2201  						}
  2202  						if v, ok := deltas[k]; ok {
  2203  							ref.MapKey.Value = d2ast.MakeValueBox(d2ast.RawString(v, false))
  2204  						}
  2205  					} else {
  2206  						deltas, err := MoveIDDeltas(tmpG, *from, *to, includeDescendants)
  2207  						if err != nil {
  2208  							return err
  2209  						}
  2210  						if v, ok := deltas[k]; ok {
  2211  							ref.MapKey.Value = d2ast.MakeValueBox(d2ast.RawString(v, false))
  2212  						}
  2213  					}
  2214  				}
  2215  			}
  2216  		}
  2217  	}
  2218  
  2219  	return nil
  2220  }
  2221  
  2222  func deleteFromMap(m *d2ast.Map, mk *d2ast.Key) bool {
  2223  	for i, n := range m.Nodes {
  2224  		if n.MapKey == mk {
  2225  			m.Nodes = append(m.Nodes[:i], m.Nodes[i+1:]...)
  2226  			return true
  2227  		}
  2228  	}
  2229  	return false
  2230  }
  2231  
  2232  func ReparentIDDelta(g *d2graph.Graph, boardPath []string, key, parentKey string) (string, error) {
  2233  	mk, err := d2parser.ParseMapKey(key)
  2234  	if err != nil {
  2235  		return "", err
  2236  	}
  2237  
  2238  	boardG := g
  2239  
  2240  	if len(boardPath) > 0 {
  2241  		// When compiling a nested board, we can read from boardG but only write to baseBoardG
  2242  		boardG = GetBoardGraph(g, boardPath)
  2243  		if boardG == nil {
  2244  			return "", fmt.Errorf("board %v not found", boardPath)
  2245  		}
  2246  	}
  2247  
  2248  	obj, ok := boardG.Root.HasChild(d2graph.Key(mk.Key))
  2249  	if !ok {
  2250  		return "", errors.New("not found")
  2251  	}
  2252  
  2253  	parent := boardG.Root
  2254  	if parentKey != "" {
  2255  		mk2, err := d2parser.ParseMapKey(parentKey)
  2256  		if err != nil {
  2257  			return "", err
  2258  		}
  2259  		parent, ok = boardG.Root.HasChild(d2graph.Key(mk2.Key))
  2260  		if !ok {
  2261  			return "", errors.New("not found")
  2262  		}
  2263  	}
  2264  
  2265  	prevParent := obj.Parent
  2266  	obj.Parent = parent
  2267  	id := obj.AbsID()
  2268  	obj.Parent = prevParent
  2269  	return id, nil
  2270  }
  2271  
  2272  func ReconnectEdgeIDDeltas(g *d2graph.Graph, boardPath []string, edgeKey string, srcKey, dstKey *string) (deltas map[string]string, err error) {
  2273  	defer xdefer.Errorf(&err, "failed to get deltas for reconnect edge %#v", edgeKey)
  2274  	deltas = make(map[string]string)
  2275  	// Reconnection: nothing is created or destroyed, the edge just gets a new ID
  2276  	// For deltas, it's indices that change:
  2277  	// - old sibling edges may decrement index
  2278  	// -- happens when the edge is not the last edge index
  2279  	// - new sibling edges may increment index
  2280  	// -- happens when the edge is not the last edge index
  2281  	// - new edge of course always needs an entry
  2282  
  2283  	// The change happens at the first ref, since that is what changes index
  2284  	mk, err := d2parser.ParseMapKey(edgeKey)
  2285  	if err != nil {
  2286  		return nil, err
  2287  	}
  2288  
  2289  	if len(mk.Edges) == 0 {
  2290  		return nil, errors.New("edgeKey must be an edge")
  2291  	}
  2292  
  2293  	if mk.EdgeIndex == nil {
  2294  		return nil, errors.New("edgeKey must refer to an existing edge")
  2295  	}
  2296  
  2297  	edgeTrimCommon(mk)
  2298  
  2299  	boardG := g
  2300  
  2301  	if len(boardPath) > 0 {
  2302  		// When compiling a nested board, we can read from boardG but only write to baseBoardG
  2303  		boardG = GetBoardGraph(g, boardPath)
  2304  		if boardG == nil {
  2305  			return nil, fmt.Errorf("board %v not found", boardPath)
  2306  		}
  2307  	}
  2308  
  2309  	obj := boardG.Root
  2310  	if mk.Key != nil {
  2311  		var ok bool
  2312  		obj, ok = boardG.Root.HasChild(d2graph.Key(mk.Key))
  2313  		if !ok {
  2314  			return nil, errors.New("edge not found")
  2315  		}
  2316  	}
  2317  	edge, ok := obj.HasEdge(mk)
  2318  	if !ok {
  2319  		return nil, errors.New("edge not found")
  2320  	}
  2321  
  2322  	if srcKey != nil {
  2323  		if edge.Src.AbsID() == *srcKey {
  2324  			srcKey = nil
  2325  		}
  2326  	}
  2327  
  2328  	if dstKey != nil {
  2329  		if edge.Dst.AbsID() == *dstKey {
  2330  			dstKey = nil
  2331  		}
  2332  	}
  2333  
  2334  	if srcKey == nil && dstKey == nil {
  2335  		return nil, nil
  2336  	}
  2337  
  2338  	newSrc := edge.Src
  2339  	newDst := edge.Dst
  2340  	var src *d2graph.Object
  2341  	var dst *d2graph.Object
  2342  	if srcKey != nil {
  2343  		srcmk, err := d2parser.ParseMapKey(*srcKey)
  2344  		if err != nil {
  2345  			return nil, err
  2346  		}
  2347  		src, ok = boardG.Root.HasChild(d2graph.Key(srcmk.Key))
  2348  		if !ok {
  2349  			return nil, errors.New("newSrc not found")
  2350  		}
  2351  		newSrc = src
  2352  	}
  2353  	if dstKey != nil {
  2354  		dstmk, err := d2parser.ParseMapKey(*dstKey)
  2355  		if err != nil {
  2356  			return nil, err
  2357  		}
  2358  		dst, ok = boardG.Root.HasChild(d2graph.Key(dstmk.Key))
  2359  		if !ok {
  2360  			return nil, errors.New("newDst not found")
  2361  		}
  2362  		newDst = dst
  2363  	}
  2364  
  2365  	// The first ref is always the definition
  2366  	firstRef := edge.References[0]
  2367  	line := firstRef.MapKey.Range.Start.Line
  2368  	newIndex := 0
  2369  
  2370  	// For the edge's own delta, it just needs to know how many edges came before it with the same src and dst
  2371  	for _, otherEdge := range boardG.Edges {
  2372  		if otherEdge.Src == newSrc && otherEdge.Dst == newDst {
  2373  			firstRef := otherEdge.References[0]
  2374  			if firstRef.MapKey.Range.Start.Line <= line {
  2375  				newIndex++
  2376  			}
  2377  		}
  2378  		if otherEdge.Src == edge.Src && otherEdge.Dst == edge.Dst && otherEdge.Index > edge.Index {
  2379  			before := otherEdge.AbsID()
  2380  			otherEdge.Index--
  2381  			after := otherEdge.AbsID()
  2382  			deltas[before] = after
  2383  			otherEdge.Index++
  2384  		}
  2385  	}
  2386  
  2387  	for _, otherEdge := range g.Edges {
  2388  		if otherEdge.Src == newSrc && otherEdge.Dst == newDst {
  2389  			if otherEdge.Index >= newIndex {
  2390  				before := otherEdge.AbsID()
  2391  				otherEdge.Index++
  2392  				after := otherEdge.AbsID()
  2393  				deltas[before] = after
  2394  				otherEdge.Index--
  2395  			}
  2396  		}
  2397  	}
  2398  
  2399  	newEdge := &d2graph.Edge{
  2400  		Src:      newSrc,
  2401  		Dst:      newDst,
  2402  		SrcArrow: edge.SrcArrow,
  2403  		DstArrow: edge.DstArrow,
  2404  		Index:    newIndex,
  2405  	}
  2406  
  2407  	deltas[edge.AbsID()] = newEdge.AbsID()
  2408  
  2409  	return deltas, nil
  2410  }
  2411  
  2412  // generateUniqueKey generates a unique key by appending a number after `prefix` such that it doesn't conflict with any IDs in `g`
  2413  // If `ignored` is not nil, a conflict with the ignored object is allowed. An example use case is to generate a unique ID for a child being
  2414  // hoisted out of its container, and you know the container is going to be deleted.
  2415  //
  2416  // If `included` is not nil, the generated key must also not conflict with a key in `included`, on top of not conflicting with any IDs in `g`.
  2417  // This is for when an operation needs to generate multiple unique keys in one go, like deleting a container and giving new IDs to all children
  2418  func generateUniqueKey(g *d2graph.Graph, prefix string, ignored *d2graph.Object, included []string) (key string, edge bool, _ error) {
  2419  	mk, err := d2parser.ParseMapKey(prefix)
  2420  	if err != nil {
  2421  		return "", false, err
  2422  	}
  2423  
  2424  	if len(mk.Edges) > 1 {
  2425  		return "", false, errors.New("cannot generate unique key for edge chain")
  2426  	}
  2427  
  2428  	if len(mk.Edges) == 1 {
  2429  		if mk.EdgeIndex == nil || mk.EdgeIndex.Int == nil {
  2430  			mk.EdgeIndex = &d2ast.EdgeIndex{
  2431  				Int: go2.Pointer(0),
  2432  			}
  2433  		}
  2434  
  2435  		edgeTrimCommon(mk)
  2436  		obj := g.Root
  2437  		if mk.Key != nil {
  2438  			var ok bool
  2439  			obj, ok = g.Root.HasChild(d2graph.Key(mk.Key))
  2440  			if !ok {
  2441  				return d2format.Format(mk), true, nil
  2442  			}
  2443  		}
  2444  		for {
  2445  			_, ok := obj.HasEdge(mk)
  2446  			if !ok {
  2447  				return d2format.Format(mk), true, nil
  2448  			}
  2449  			mk.EdgeIndex.Int = go2.Pointer(*mk.EdgeIndex.Int + 1)
  2450  		}
  2451  	}
  2452  
  2453  	// If a key is not provided, we generate one.
  2454  	if mk.Key == nil {
  2455  		mk.Key = &d2ast.KeyPath{
  2456  			Path: []*d2ast.StringBox{d2ast.MakeValueBox(d2ast.RawString(xrand.Base64(16), true)).StringBox()},
  2457  		}
  2458  	} else if obj, ok := g.Root.HasChild(d2graph.Key(mk.Key)); ok && obj != ignored {
  2459  		// The key may already have an index, e.g. "x 2"
  2460  		spaced := strings.Split(prefix, " ")
  2461  		if len(spaced) > 1 {
  2462  			if _, err := strconv.Atoi(spaced[len(spaced)-1]); err == nil {
  2463  				withoutIndex := strings.Join(spaced[:len(spaced)-1], " ")
  2464  				mk, err = d2parser.ParseMapKey(withoutIndex)
  2465  				if err != nil {
  2466  					return "", false, err
  2467  				}
  2468  			}
  2469  		}
  2470  	}
  2471  
  2472  	k2 := cloneKey(mk.Key)
  2473  	i := 0
  2474  	for {
  2475  		conflictsWithIncluded := false
  2476  		for _, s := range included {
  2477  			if d2format.Format(k2) == s {
  2478  				conflictsWithIncluded = true
  2479  				break
  2480  			}
  2481  		}
  2482  		if !conflictsWithIncluded {
  2483  			obj, ok := g.Root.HasChild(d2graph.Key(k2))
  2484  			if !ok || obj == ignored {
  2485  				return d2format.Format(k2), false, nil
  2486  			}
  2487  		}
  2488  
  2489  		rr := fmt.Sprintf("%s %d", mk.Key.Path[len(mk.Key.Path)-1].Unbox().ScalarString(), i+2)
  2490  		k2.Path[len(k2.Path)-1] = d2ast.MakeValueBox(d2ast.RawString(rr, true)).StringBox()
  2491  		i++
  2492  	}
  2493  }
  2494  
  2495  func cloneKey(k *d2ast.KeyPath) *d2ast.KeyPath {
  2496  	if k == nil {
  2497  		return &d2ast.KeyPath{}
  2498  	}
  2499  	tmp := *k
  2500  	k2 := &tmp
  2501  	k2.Path = nil
  2502  	for _, p := range k.Path {
  2503  		k2.Path = append(k2.Path, d2ast.MakeValueBox(p.Unbox().Copy()).StringBox())
  2504  	}
  2505  	return k2
  2506  }
  2507  
  2508  func getCommonPath(a, b []string) []string {
  2509  	var out []string
  2510  	for i := 0; i < len(a) && i < len(b); i++ {
  2511  		if a[i] == b[i] {
  2512  			out = a[:i+1]
  2513  		}
  2514  	}
  2515  	return out
  2516  }
  2517  
  2518  func getUncommonPath(a, b []string) []string {
  2519  	var out []string
  2520  	for i := 0; i < len(a) && i < len(b); i++ {
  2521  		if a[i] != b[i] {
  2522  			out = a[:i+1]
  2523  		}
  2524  	}
  2525  	return out
  2526  }
  2527  
  2528  func edgeTrimCommon(mk *d2ast.Key) {
  2529  	if len(mk.Edges) != 1 {
  2530  		return
  2531  	}
  2532  	e := mk.Edges[0]
  2533  	for len(e.Src.Path) > 1 && len(e.Dst.Path) > 1 {
  2534  		if !strings.EqualFold(e.Src.Path[0].Unbox().ScalarString(), e.Dst.Path[0].Unbox().ScalarString()) {
  2535  			return
  2536  		}
  2537  		if mk.Key == nil {
  2538  			mk.Key = &d2ast.KeyPath{}
  2539  		}
  2540  		mk.Key.Path = append(mk.Key.Path, e.Src.Path[0])
  2541  		e.Src.Path = e.Src.Path[1:]
  2542  		e.Dst.Path = e.Dst.Path[1:]
  2543  	}
  2544  }
  2545  
  2546  func MoveIDDeltas(g *d2graph.Graph, key, newKey string, includeDescendants bool) (deltas map[string]string, err error) {
  2547  	defer xdefer.Errorf(&err, "failed to get deltas for move from %#v to %#v", key, newKey)
  2548  	deltas = make(map[string]string)
  2549  
  2550  	if key == newKey {
  2551  		return deltas, nil
  2552  	}
  2553  
  2554  	mk, err := d2parser.ParseMapKey(key)
  2555  	if err != nil {
  2556  		return nil, err
  2557  	}
  2558  
  2559  	newKey, _, err = generateUniqueKey(g, newKey, nil, nil)
  2560  	if err != nil {
  2561  		return nil, err
  2562  	}
  2563  
  2564  	mk2, err := d2parser.ParseMapKey(newKey)
  2565  	if err != nil {
  2566  		return nil, err
  2567  	}
  2568  
  2569  	ak := d2graph.Key(mk.Key)
  2570  	ak2 := d2graph.Key(mk2.Key)
  2571  	isCrossScope := strings.Join(ak[:len(ak)-1], ".") != strings.Join(ak2[:len(ak2)-1], ".")
  2572  
  2573  	edgeTrimCommon(mk)
  2574  	obj := g.Root
  2575  
  2576  	// Conflict IDs are when a container is moved and the children conflict with something in parent
  2577  	conflictNewIDs := make(map[*d2graph.Object]string)
  2578  	conflictOldIDs := make(map[*d2graph.Object]string)
  2579  	var newIDs []string
  2580  	if mk.Key != nil {
  2581  		var ok bool
  2582  		obj, ok = g.Root.HasChild(d2graph.Key(mk.Key))
  2583  		if !ok {
  2584  			return nil, nil
  2585  		}
  2586  
  2587  		ignored := obj
  2588  		for _, ch := range obj.ChildrenArray {
  2589  			if ch.ID == obj.ID {
  2590  				ignored = nil
  2591  				break
  2592  			}
  2593  		}
  2594  
  2595  		if !includeDescendants {
  2596  			for _, ch := range obj.ChildrenArray {
  2597  				chMK, err := d2parser.ParseMapKey(ch.AbsID())
  2598  				if err != nil {
  2599  					return nil, err
  2600  				}
  2601  				ida := d2graph.Key(chMK.Key)
  2602  				if ida[len(ida)-1] == ida[len(ida)-2] {
  2603  					continue
  2604  				}
  2605  
  2606  				hoistedAbsID := ch.ID
  2607  				if obj.Parent != g.Root {
  2608  					hoistedAbsID = obj.Parent.AbsID() + "." + ch.ID
  2609  				}
  2610  				hoistedMK, err := d2parser.ParseMapKey(hoistedAbsID)
  2611  				if err != nil {
  2612  					return nil, err
  2613  				}
  2614  
  2615  				conflictsWithNewID := false
  2616  				for _, id := range newIDs {
  2617  					if id == d2format.Format(hoistedMK.Key) {
  2618  						conflictsWithNewID = true
  2619  						break
  2620  					}
  2621  				}
  2622  
  2623  				if _, ok := g.Root.HasChild(d2graph.Key(hoistedMK.Key)); ok || conflictsWithNewID {
  2624  					newKey, _, err := generateUniqueKey(g, hoistedAbsID, ignored, newIDs)
  2625  					if err != nil {
  2626  						return nil, err
  2627  					}
  2628  					newMK, err := d2parser.ParseMapKey(newKey)
  2629  					if err != nil {
  2630  						return nil, err
  2631  					}
  2632  					newAK := d2graph.Key(newMK.Key)
  2633  					conflictOldIDs[ch] = ch.ID
  2634  					conflictNewIDs[ch] = newAK[len(newAK)-1]
  2635  					newIDs = append(newIDs, d2format.Format(newMK.Key))
  2636  				} else {
  2637  					newIDs = append(newIDs, d2format.Format(hoistedMK.Key))
  2638  				}
  2639  			}
  2640  		}
  2641  	}
  2642  
  2643  	if len(mk.Edges) > 1 {
  2644  		return nil, nil
  2645  	}
  2646  	if len(mk.Edges) == 1 {
  2647  		if len(mk.Edges) == 0 {
  2648  			return nil, errors.New("cannot rename edge to node")
  2649  		}
  2650  		if len(mk.Edges) > 1 {
  2651  			return nil, errors.New("cannot rename edge to edge chain")
  2652  		}
  2653  
  2654  		e, ok := obj.HasEdge(mk)
  2655  		if !ok {
  2656  			return nil, nil
  2657  		}
  2658  		beforeID := e.AbsID()
  2659  		tmp := *e
  2660  		e2 := &tmp
  2661  		e2.SrcArrow = mk2.Edges[0].SrcArrow == "<"
  2662  		e2.DstArrow = mk2.Edges[0].DstArrow == ">"
  2663  		deltas[beforeID] = e2.AbsID()
  2664  		return deltas, nil
  2665  	}
  2666  
  2667  	beforeObjID := obj.ID
  2668  
  2669  	toParent := g.Root
  2670  	if len(ak2) > 1 {
  2671  		var ok bool
  2672  		toParent, ok = g.Root.HasChild(ak2[:len(ak2)-1])
  2673  		if !ok {
  2674  			return nil, errors.New("to parent not found")
  2675  		}
  2676  	}
  2677  	id := ak2[len(ak2)-1]
  2678  
  2679  	tmpRenames := func() func() {
  2680  		if isCrossScope && !includeDescendants {
  2681  			for _, ch := range obj.ChildrenArray {
  2682  				ch.Parent = obj.Parent
  2683  			}
  2684  		}
  2685  
  2686  		prevParent := obj.Parent
  2687  		obj.Parent = toParent
  2688  		obj.ID = id
  2689  
  2690  		for k, v := range conflictNewIDs {
  2691  			k.ID = v
  2692  		}
  2693  		return func() {
  2694  			for k, v := range conflictOldIDs {
  2695  				k.ID = v
  2696  			}
  2697  			obj.ID = beforeObjID
  2698  			obj.Parent = prevParent
  2699  
  2700  			if isCrossScope && !includeDescendants {
  2701  				for _, ch := range obj.ChildrenArray {
  2702  					ch.Parent = obj
  2703  				}
  2704  			}
  2705  		}
  2706  	}
  2707  
  2708  	appendNodeDelta := func(ch *d2graph.Object) {
  2709  		beforeID := ch.AbsID()
  2710  		revert := tmpRenames()
  2711  		deltas[beforeID] = ch.AbsID()
  2712  		revert()
  2713  	}
  2714  
  2715  	appendEdgeDelta := func(ch *d2graph.Object) {
  2716  		for _, e := range obj.Graph.Edges {
  2717  			if e.Src == ch || e.Dst == ch {
  2718  				beforeID := e.AbsID()
  2719  				revert := tmpRenames()
  2720  				deltas[beforeID] = e.AbsID()
  2721  				revert()
  2722  			}
  2723  		}
  2724  	}
  2725  
  2726  	var recurse func(ch *d2graph.Object)
  2727  	recurse = func(ch *d2graph.Object) {
  2728  		for _, ch := range ch.ChildrenArray {
  2729  			appendNodeDelta(ch)
  2730  			appendEdgeDelta(ch)
  2731  			recurse(ch)
  2732  		}
  2733  	}
  2734  	appendNodeDelta(obj)
  2735  	appendEdgeDelta(obj)
  2736  	recurse(obj)
  2737  	return deltas, nil
  2738  }
  2739  
  2740  func DeleteIDDeltas(g *d2graph.Graph, boardPath []string, key string) (deltas map[string]string, err error) {
  2741  	defer xdefer.Errorf(&err, "failed to get deltas for deletion of %#v", key)
  2742  	deltas = make(map[string]string)
  2743  
  2744  	mk, err := d2parser.ParseMapKey(key)
  2745  	if err != nil {
  2746  		return nil, err
  2747  	}
  2748  
  2749  	edgeTrimCommon(mk)
  2750  
  2751  	boardG := g
  2752  	if len(boardPath) > 0 {
  2753  		boardG = GetBoardGraph(g, boardPath)
  2754  		if boardG == nil {
  2755  			return nil, fmt.Errorf("board %v not found", boardPath)
  2756  		}
  2757  	}
  2758  
  2759  	obj := boardG.Root
  2760  	conflictNewIDs := make(map[*d2graph.Object]string)
  2761  	conflictOldIDs := make(map[*d2graph.Object]string)
  2762  	var newIDs []string
  2763  	if mk.Key != nil {
  2764  		ida := d2graph.Key(mk.Key)
  2765  		// Deleting a reserved field cannot possibly have any deltas
  2766  		if _, ok := d2graph.ReservedKeywords[ida[len(ida)-1]]; ok {
  2767  			return nil, nil
  2768  		}
  2769  
  2770  		var ok bool
  2771  		obj, ok = boardG.Root.HasChild(d2graph.Key(mk.Key))
  2772  		if !ok {
  2773  			return nil, nil
  2774  		}
  2775  
  2776  		ignored := obj
  2777  		for _, ch := range obj.ChildrenArray {
  2778  			if ch.ID == obj.ID {
  2779  				ignored = nil
  2780  				break
  2781  			}
  2782  		}
  2783  
  2784  		for _, ch := range obj.ChildrenArray {
  2785  			// Record siblings as the unique key generated should not conflict with any siblings either
  2786  			var siblingsToBeHoisted []string
  2787  			for _, ch2 := range obj.ChildrenArray {
  2788  				if ch2 != ch {
  2789  					chMK, err := d2parser.ParseMapKey(ch2.AbsID())
  2790  					if err != nil {
  2791  						return nil, err
  2792  					}
  2793  					ida := d2graph.Key(chMK.Key)
  2794  					if ida[len(ida)-1] == ida[len(ida)-2] {
  2795  						continue
  2796  					}
  2797  					hoistedAbsID := ch2.ID
  2798  					if obj.Parent != boardG.Root {
  2799  						hoistedAbsID = obj.Parent.AbsID() + "." + ch2.ID
  2800  					}
  2801  					siblingsToBeHoisted = append(siblingsToBeHoisted, hoistedAbsID)
  2802  				}
  2803  			}
  2804  			chMK, err := d2parser.ParseMapKey(ch.AbsID())
  2805  			if err != nil {
  2806  				return nil, err
  2807  			}
  2808  			ida := d2graph.Key(chMK.Key)
  2809  			if ida[len(ida)-1] == ida[len(ida)-2] {
  2810  				continue
  2811  			}
  2812  			hoistedAbsID := ch.ID
  2813  			if obj.Parent != boardG.Root {
  2814  				hoistedAbsID = obj.Parent.AbsID() + "." + ch.ID
  2815  			}
  2816  			hoistedMK, err := d2parser.ParseMapKey(hoistedAbsID)
  2817  			if err != nil {
  2818  				return nil, err
  2819  			}
  2820  
  2821  			conflictsWithNewID := false
  2822  			for _, id := range newIDs {
  2823  				if id == d2format.Format(hoistedMK.Key) {
  2824  					conflictsWithNewID = true
  2825  					break
  2826  				}
  2827  			}
  2828  
  2829  			if conflictingObj, ok := boardG.Root.HasChild(d2graph.Key(hoistedMK.Key)); (ok && conflictingObj != obj) || conflictsWithNewID {
  2830  				newKey, _, err := generateUniqueKey(boardG, hoistedAbsID, ignored, append(newIDs, siblingsToBeHoisted...))
  2831  				if err != nil {
  2832  					return nil, err
  2833  				}
  2834  				newMK, err := d2parser.ParseMapKey(newKey)
  2835  				if err != nil {
  2836  					return nil, err
  2837  				}
  2838  				newAK := d2graph.Key(newMK.Key)
  2839  				conflictOldIDs[ch] = ch.ID
  2840  				conflictNewIDs[ch] = newAK[len(newAK)-1]
  2841  				newIDs = append(newIDs, d2format.Format(newMK.Key))
  2842  			} else {
  2843  				newIDs = append(newIDs, d2format.Format(hoistedMK.Key))
  2844  			}
  2845  		}
  2846  	}
  2847  	if len(mk.Edges) > 1 {
  2848  		return nil, nil
  2849  	}
  2850  	if len(mk.Edges) == 1 {
  2851  		// Anything deleted in an edge key cannot affect deltas
  2852  		if mk.EdgeKey != nil {
  2853  			return nil, nil
  2854  		}
  2855  		e, ok := obj.HasEdge(mk)
  2856  		if !ok {
  2857  			return nil, nil
  2858  		}
  2859  		ea, ok := obj.FindEdges(mk)
  2860  		if !ok {
  2861  			return nil, nil
  2862  		}
  2863  		for _, e2 := range ea {
  2864  			if e2.Index > e.Index {
  2865  				beforeID := e2.AbsID()
  2866  				e2.Index--
  2867  				deltas[beforeID] = e2.AbsID()
  2868  				e2.Index++
  2869  			}
  2870  		}
  2871  		return deltas, nil
  2872  	}
  2873  
  2874  	for _, ch := range obj.ChildrenArray {
  2875  		tmpRenames := func() func() {
  2876  			prevIDs := make(map[*d2graph.Object]string)
  2877  			for _, ch := range obj.ChildrenArray {
  2878  				prevIDs[ch] = ch.ID
  2879  				ch.Parent = obj.Parent
  2880  			}
  2881  			for k, v := range conflictNewIDs {
  2882  				k.ID = v
  2883  			}
  2884  
  2885  			return func() {
  2886  				for k, v := range conflictOldIDs {
  2887  					k.ID = v
  2888  				}
  2889  				for _, ch := range obj.ChildrenArray {
  2890  					ch.Parent = obj
  2891  					ch.ID = prevIDs[ch]
  2892  				}
  2893  			}
  2894  		}
  2895  
  2896  		appendNodeDelta := func(ch2 *d2graph.Object) {
  2897  			beforeAbsID := ch2.AbsID()
  2898  			revert := tmpRenames()
  2899  			deltas[beforeAbsID] = ch2.AbsID()
  2900  			revert()
  2901  
  2902  		}
  2903  		appendEdgeDelta := func(ch2 *d2graph.Object) {
  2904  			for _, e := range obj.Graph.Edges {
  2905  				if e.Src == ch2 || e.Dst == ch2 {
  2906  					beforeAbsID := e.AbsID()
  2907  					revert := tmpRenames()
  2908  					deltas[beforeAbsID] = e.AbsID()
  2909  					revert()
  2910  
  2911  				}
  2912  			}
  2913  		}
  2914  
  2915  		var recurse func(ch2 *d2graph.Object)
  2916  		recurse = func(ch2 *d2graph.Object) {
  2917  			for _, ch2 := range ch2.ChildrenArray {
  2918  				appendNodeDelta(ch2)
  2919  				appendEdgeDelta(ch2)
  2920  				recurse(ch2)
  2921  			}
  2922  		}
  2923  
  2924  		appendNodeDelta(ch)
  2925  		appendEdgeDelta(ch)
  2926  		recurse(ch)
  2927  	}
  2928  	return deltas, nil
  2929  }
  2930  
  2931  func RenameIDDeltas(g *d2graph.Graph, boardPath []string, key, newName string) (deltas map[string]string, err error) {
  2932  	defer xdefer.Errorf(&err, "failed to get deltas for renaming of %#v to %#v", key, newName)
  2933  	deltas = make(map[string]string)
  2934  
  2935  	mk, err := d2parser.ParseMapKey(key)
  2936  	if err != nil {
  2937  		return nil, err
  2938  	}
  2939  
  2940  	boardG := g
  2941  	if len(boardPath) > 0 {
  2942  		boardG = GetBoardGraph(g, boardPath)
  2943  		if boardG == nil {
  2944  			return nil, fmt.Errorf("board %v not found", boardPath)
  2945  		}
  2946  	}
  2947  
  2948  	edgeTrimCommon(mk)
  2949  	obj := boardG.Root
  2950  	if mk.Key != nil {
  2951  		var ok bool
  2952  		obj, ok = boardG.Root.HasChild(d2graph.Key(mk.Key))
  2953  		if !ok {
  2954  			return nil, nil
  2955  		}
  2956  	}
  2957  	if len(mk.Edges) > 1 {
  2958  		return nil, nil
  2959  	}
  2960  	if len(mk.Edges) == 1 {
  2961  		mk2, err := d2parser.ParseMapKey(newName)
  2962  		if err != nil {
  2963  			return nil, err
  2964  		}
  2965  		if len(mk.Edges) == 0 {
  2966  			return nil, errors.New("cannot rename edge to node")
  2967  		}
  2968  		if len(mk.Edges) > 1 {
  2969  			return nil, errors.New("cannot rename edge to edge chain")
  2970  		}
  2971  
  2972  		e, ok := obj.HasEdge(mk)
  2973  		if !ok {
  2974  			return nil, nil
  2975  		}
  2976  		beforeID := e.AbsID()
  2977  		tmp := *e
  2978  		e2 := &tmp
  2979  		e2.SrcArrow = mk2.Edges[0].SrcArrow == "<"
  2980  		e2.DstArrow = mk2.Edges[0].DstArrow == ">"
  2981  		deltas[beforeID] = e2.AbsID()
  2982  		return deltas, nil
  2983  	}
  2984  
  2985  	if mk.Key.Path[len(mk.Key.Path)-1].Unbox().ScalarString() == newName {
  2986  		return deltas, nil
  2987  	}
  2988  
  2989  	mk.Key.Path[len(mk.Key.Path)-1].Unbox().SetString(newName)
  2990  	uniqueKeyStr, _, err := generateUniqueKey(boardG, strings.Join(d2graph.Key(mk.Key), "."), obj, nil)
  2991  	if err != nil {
  2992  		return nil, err
  2993  	}
  2994  	uniqueKey, err := d2parser.ParseKey(uniqueKeyStr)
  2995  	if err != nil {
  2996  		return nil, err
  2997  	}
  2998  	newNameKey := uniqueKey.Path[len(uniqueKey.Path)-1].Unbox().ScalarString()
  2999  	newNameKey = d2format.Format(d2ast.RawString(newNameKey, true))
  3000  
  3001  	beforeObjID := obj.ID
  3002  
  3003  	appendNodeDelta := func(ch *d2graph.Object) {
  3004  		if obj.ID != newNameKey {
  3005  			beforeID := ch.AbsID()
  3006  			obj.ID = newNameKey
  3007  			deltas[beforeID] = ch.AbsID()
  3008  			obj.ID = beforeObjID
  3009  		}
  3010  	}
  3011  
  3012  	appendEdgeDelta := func(ch *d2graph.Object) {
  3013  		for _, e := range obj.Graph.Edges {
  3014  			if e.Src == ch || e.Dst == ch {
  3015  				if obj.ID != newNameKey {
  3016  					beforeID := e.AbsID()
  3017  					obj.ID = newNameKey
  3018  					deltas[beforeID] = e.AbsID()
  3019  					obj.ID = beforeObjID
  3020  				}
  3021  			}
  3022  		}
  3023  	}
  3024  
  3025  	var recurse func(ch *d2graph.Object)
  3026  	recurse = func(ch *d2graph.Object) {
  3027  		for _, ch := range ch.ChildrenArray {
  3028  			appendNodeDelta(ch)
  3029  			appendEdgeDelta(ch)
  3030  			recurse(ch)
  3031  		}
  3032  	}
  3033  	appendNodeDelta(obj)
  3034  	appendEdgeDelta(obj)
  3035  	recurse(obj)
  3036  	return deltas, nil
  3037  }
  3038  
  3039  func hasSpace(tag string) bool {
  3040  	for _, r := range tag {
  3041  		if unicode.IsSpace(r) {
  3042  			return true
  3043  		}
  3044  	}
  3045  	return false
  3046  }
  3047  
  3048  func getMostNestedRefs(obj *d2graph.Object) []d2graph.Reference {
  3049  	var most d2graph.Reference
  3050  	for _, ref := range obj.References {
  3051  		if len(ref.MapKey.Edges) == 0 {
  3052  			most = ref
  3053  			break
  3054  		}
  3055  	}
  3056  	for _, ref := range obj.References {
  3057  		if len(ref.MapKey.Edges) != 0 {
  3058  			continue
  3059  		}
  3060  
  3061  		scopeKey, err := d2parser.ParseKey(ref.ScopeObj.AbsID())
  3062  		if err != nil {
  3063  			scopeKey = &d2ast.KeyPath{}
  3064  		}
  3065  		mostKey, err := d2parser.ParseKey(most.ScopeObj.AbsID())
  3066  		if err != nil {
  3067  			mostKey = &d2ast.KeyPath{}
  3068  		}
  3069  		_, resolvedScopeKey, err := d2graph.ResolveUnderscoreKey(d2graph.Key(scopeKey), ref.ScopeObj)
  3070  		if err != nil {
  3071  			continue
  3072  		}
  3073  		_, resolvedMostKey, err := d2graph.ResolveUnderscoreKey(d2graph.Key(mostKey), ref.ScopeObj)
  3074  		if err != nil {
  3075  			continue
  3076  		}
  3077  		if len(resolvedScopeKey) > len(resolvedMostKey) {
  3078  			most = ref
  3079  		}
  3080  	}
  3081  
  3082  	var out []d2graph.Reference
  3083  	for _, ref := range obj.References {
  3084  		if len(ref.MapKey.Edges) != 0 {
  3085  			continue
  3086  		}
  3087  		if ref.ScopeObj.AbsID() == most.ScopeObj.AbsID() {
  3088  			out = append(out, ref)
  3089  		}
  3090  	}
  3091  
  3092  	return out
  3093  }
  3094  
  3095  func filterReservedPath(path []*d2ast.StringBox) (filtered []*d2ast.StringBox) {
  3096  	for _, box := range path {
  3097  		if _, ok := d2graph.ReservedKeywords[strings.ToLower(box.Unbox().ScalarString())]; ok {
  3098  			return
  3099  		}
  3100  		filtered = append(filtered, box)
  3101  	}
  3102  	return
  3103  }
  3104  
  3105  func getWriteableRefs(obj *d2graph.Object, writeableAST *d2ast.Map) (out []d2graph.Reference) {
  3106  	for i, ref := range obj.References {
  3107  		if ref.ScopeAST == writeableAST {
  3108  			out = append(out, obj.References[i])
  3109  		}
  3110  	}
  3111  	return
  3112  }
  3113  
  3114  func getWriteableEdgeRefs(edge *d2graph.Edge, writeableAST *d2ast.Map) (out []d2graph.EdgeReference) {
  3115  	for i, ref := range edge.References {
  3116  		if ref.ScopeAST == writeableAST {
  3117  			out = append(out, edge.References[i])
  3118  		}
  3119  	}
  3120  	return
  3121  }
  3122  

View as plain text