← Volver al sitio
SKILL · Cadence Virtuoso

→ OPAMP/BufferSortida/schematic_RailToRail_0309_20261008T094410
SeguidorVersiones.il
;; =========================================================================
;; PLUGIN: SEGUIDOR DE VERSIONES DE ESQUEMATICOS
;; Cada vez que se guarda un esquematico (lo detecta por la fecha del sch.oa)
;; rota dos instantaneas dentro de la misma celda:
;;   vtprev__<vista>   version guardada anterior
;;   vtcur__<vista>    version guardada actual
;; Version: 2026-10-08
;;
;; Menu "Seguidor de versiones":
;;   - Guardar version actual como vista con fecha ISO 8601 o nombre propio
;;     Atajo de teclado (vtTeclaGuardar, por defecto Ctrl+Q): abre el
;;     dialogo con "Fecha y hora actual" ya marcado; Intro y listo.
;;   - Diferencias: anterior vs estado en edicion (incluye cambios sin guardar)
;;   - Comparar versiones: dos versiones cualesquiera de la celda (las de
;;     fecha, las de nombre especial, la anterior o el estado en edicion)
;;   - Volver: intercambia actual y anterior (repetirlo deshace)
;;   - Abrir la anterior en solo lectura
;; Los nombres de vista no empiezan por "schematic": el plugin gm/Id no las ve.
;; =========================================================================

;; Se reinicia en cada carga: asi un reload vuelve a crear las instantaneas
;; que falten
vtMtimes = makeTable('vtMtimes nil)
;; hiRegTimer va en decimas de segundo: 10 = 1 s
unless( boundp('vtPeriodo) vtPeriodo = 10 )
unless( boundp('vtDirInforme) vtDirInforme = "/tmp" )
;; Atajo para "Guardar version actual" (sintaxis de hiSetBindKey) y su texto
;; en el menu. Cambialos antes de cargar el archivo si chocan con otro atajo.
unless( boundp('vtTeclaGuardar) vtTeclaGuardar = "Ctrl<Key>q" )
unless( boundp('vtTeclaTexto) vtTeclaTexto = "Ctrl+Q" )
vtBusy = nil

;; -------------------------------------------------------------------------
;; UTILIDADES
;; -------------------------------------------------------------------------
procedure( vtVista(view tag)
    sprintf(nil "vt%s__%s" tag view)
)

procedure( vtEsInstantanea(view)
    rexMatchp("^vt[a-z]+__" view)
)

procedure( vtClave(lib cell view)
    sprintf(nil "%s/%s/%s" lib cell view)
)

procedure( vtExiste(lib cell view)
    if( ddGetObj(lib cell view) then t else nil)
)

procedure( vtRutaOa(lib cell view)
    let( (obj)
        obj = ddGetObj(lib cell view "sch.oa")
        if( obj then obj~>readPath else nil)
    )
)

procedure( vtMtime(lib cell view)
    let( (p)
        p = vtRutaOa(lib cell view)
        if( p && isFile(p) then fileTimeModified(p) else nil)
    )
)

procedure( vtFecha(lib cell view)
    let( (mt r)
        mt = vtMtime(lib cell view)
        r = if( mt then errset(timeToString(mt)) else nil)
        if( r then car(r) else "?")
    )
)

;; Copia una vista dentro de la misma celda (sobrescribe) via base de datos.
;; Si la vista origen esta abierta en edicion se copia lo que hay en memoria,
;; por eso vtVigilar solo copia cuando no hay cambios sin guardar.
procedure( vtCopiar(lib cell vSrc vDst)
    let( (srcRes src r nuevo)
        srcRes = errset(dbOpenCellViewByType(lib cell vSrc nil "r") t)
        src = if( srcRes then car(srcRes) else nil)
        r = if( src then errset(dbCopyCellView(src lib cell vDst nil nil t) t) else nil)
        nuevo = if( r then car(r) else nil)
        when( nuevo && dbobjectp(nuevo) && nuevo != src
            dbClose(nuevo)
        )
        ;; Si src era la vista en edicion, esto solo baja su contador de uso
        when( src dbClose(src) )
        unless( nuevo
            printf("Seguidor de versiones: fallo al copiar %s -> %s en %s/%s\n"
                vSrc vDst lib cell))
        if( nuevo then t else nil)
    )
)

procedure( vtBorrar(lib cell view)
    let( (obj)
        obj = ddGetObj(lib cell view)
        when( obj errset(ddDeleteObj(obj)) )
    )
)

;; t si w es una ventana grafica que muestra un esquematico. No llama a
;; ninguna funcion ge*, asi que no genera warnings en otras ventanas.
procedure( vtEsVentanaSch(w)
    let( (r)
        r = errset(
            windowp(w) && dbobjectp(w~>cellView) &&
            w~>cellView~>cellViewType == "schematic")
        if( r && car(r) then t else nil)
    )
)

;; Esquematico en edicion de la ventana actual o, si no es un esquematico,
;; de la primera ventana de esquematico abierta
procedure( vtCvActual()
    let( (w res)
        w = hiGetCurrentWindow()
        if( vtEsVentanaSch(w) then
            res = geGetEditCellView(w)
        else
            res = nil
            foreach( x hiGetWindowList()
                when( !res && vtEsVentanaSch(x)
                    res = geGetEditCellView(x)
                )
            )
        )
        res
    )
)

;; Esquematico editable activo, o nil con mensaje
procedure( vtContexto()
    let( (cv)
        cv = vtCvActual()
        cond(
            ( !cv || cv~>cellViewType != "schematic"
                printf("Seguidor de versiones: abre un esquematico.\n")
                nil )
            ( vtEsInstantanea(cv~>viewName)
                printf("Seguidor de versiones: estas en una instantanea, vuelve a la vista de trabajo.\n")
                nil )
            ( t cv )
        )
    )
)

;; -------------------------------------------------------------------------
;; ROTACION
;; -------------------------------------------------------------------------
procedure( vtRotar(lib cell view)
    let( (vPrev vCur)
        vPrev = vtVista(view "prev")
        vCur = vtVista(view "cur")
        when( vtExiste(lib cell vCur)
            vtCopiar(lib cell vCur vPrev)
        )
        vtCopiar(lib cell view vCur)
        printf("Seguidor de versiones: %s guardado, versiones rotadas\n"
            vtClave(lib cell view))
    )
)

;; Detecta guardados comparando la fecha del sch.oa con la ultima vista
procedure( vtVigilar(cv)
    let( (lib cell view key mt ant vCur mc)
        lib = cv~>libName
        cell = cv~>cellName
        view = cv~>viewName
        unless( vtEsInstantanea(view)
            key = vtClave(lib cell view)
            mt = vtMtime(lib cell view)
            ant = vtMtimes[key]
            ;; Solo con memoria == disco; si no, se reintenta en el siguiente tick
            when( mt && !cv~>modifiedButNotSaved
                cond(
                    ;; Primera vez en esta sesion: asegura una instantanea actual
                    ( !ant
                        vtMtimes[key] = mt
                        vCur = vtVista(view "cur")
                        mc = vtMtime(lib cell vCur)
                        cond(
                            ( !mc vtCopiar(lib cell view vCur) )
                            ( mc < mt vtRotar(lib cell view) )
                        )
                    )
                    ( mt != ant
                        vtMtimes[key] = mt
                        vtRotar(lib cell view)
                    )
                )
            )
        )
    )
)

;; -------------------------------------------------------------------------
;; DIFERENCIAS
;; -------------------------------------------------------------------------
procedure( vtUnion(a b)
    let( (tbl)
        tbl = makeTable('vtU nil)
        foreach( x append(a b) tbl[x] = t )
        sort(tbl~>? 'alphalessp)
    )
)

;; Redes con nombre automatico (netNN) se identifican por lo que conectan,
;; para que renombrados internos no aparezcan como cambios
procedure( vtCanonNet(net)
    let( (nm miembros)
        nm = net~>name
        if( rexMatchp("^net[0-9]+$" nm) then
            miembros = mapcar(lambda((it) sprintf(nil "%s.%s" it~>inst~>name it~>name))
                net~>instTerms)
            sprintf(nil "{%s}" buildString(sort(miembros 'alphalessp) " "))
        else
            nm
        )
    )
)

;; list(tablaInstancias alistPines)
;; tablaInstancias[nombre] = list(master alistParams alistConexiones)
procedure( vtExtraer(cv)
    let( (ti pines params conns)
        ti = makeTable('vtInst nil)
        foreach( inst cv~>instances
            params = nil
            foreach( p inst~>prop
                params = cons(list(p~>name sprintf(nil "%L" p~>value)) params)
            )
            conns = nil
            foreach( it inst~>instTerms
                conns = cons(list(it~>name
                    if( it~>net then vtCanonNet(it~>net) else "<sin red>")) conns)
            )
            ti[inst~>name] = list(
                sprintf(nil "%s/%s/%s" inst~>libName inst~>cellName inst~>viewName)
                params conns)
        )
        pines = nil
        foreach( term cv~>terminals
            pines = cons(list(term~>name sprintf(nil "%L" term~>direction)) pines)
        )
        list(ti pines)
    )
)

;; Diferencias entre dos alists (nombre valor) -> lista de lineas
procedure( vtDiffAlist(pref a b)
    let( (lin ea eb)
        lin = nil
        foreach( n vtUnion(mapcar('car a) mapcar('car b))
            ea = assoc(n a)
            eb = assoc(n b)
            cond(
                ( ea && !eb
                    lin = cons(sprintf(nil "%s%s: %s -> (eliminado)" pref n cadr(ea)) lin) )
                ( !ea && eb
                    lin = cons(sprintf(nil "%s%s: (nuevo) -> %s" pref n cadr(eb)) lin) )
                ( cadr(ea) != cadr(eb)
                    lin = cons(sprintf(nil "%s%s: %s -> %s" pref n cadr(ea) cadr(eb)) lin) )
            )
        )
        reverse(lin)
    )
)

procedure( vtCompararCv(cvA cvB)
    let( (a b ia ib ea eb lin)
        a = vtExtraer(cvA)
        b = vtExtraer(cvB)
        ia = car(a)
        ib = car(b)
        lin = nil
        foreach( n vtUnion(ia~>? ib~>?)
            ea = ia[n]
            eb = ib[n]
            cond(
                ( ea && !eb
                    lin = cons(sprintf(nil "- %s  (%s)" n car(ea)) lin) )
                ( !ea && eb
                    lin = cons(sprintf(nil "+ %s  (%s)" n car(eb)) lin) )
                ( t
                    when( car(ea) != car(eb)
                        lin = cons(sprintf(nil "~ %s  celda: %s -> %s" n car(ea) car(eb)) lin)
                    )
                    foreach( l vtDiffAlist(sprintf(nil "~ %s  [par] " n) cadr(ea) cadr(eb))
                        lin = cons(l lin)
                    )
                    foreach( l vtDiffAlist(sprintf(nil "~ %s  [con] " n) caddr(ea) caddr(eb))
                        lin = cons(l lin)
                    )
                )
            )
        )
        foreach( l vtDiffAlist("~ [pin] " cadr(a) cadr(b))
            lin = cons(l lin)
        )
        reverse(lin)
    )
)

;; Escribe y abre el informe de diferencias de cvA (version A) a cvB (version B)
procedure( vtEscribirInforme(lib cell etqA cvA etqB cvB sufijo)
    let( (lin fich fid)
        lin = vtCompararCv(cvA cvB)
        fich = sprintf(nil "%s/vt_diff_%s_%s_%s.txt" vtDirInforme lib cell sufijo)
        fid = outfile(fich)
        when( fid
            fprintf(fid "%s/%s\n" lib cell)
            fprintf(fid "Version A: %s\n" etqA)
            fprintf(fid "Version B: %s\n\n" etqB)
            fprintf(fid "De A a B: + anadida  - eliminada  ~ modificada | [par] parametro  [con] conexion  [pin] pin\n")
            fprintf(fid "{a.D b.G}: red sin nombre, identificada por lo que conecta\n\n")
            if( lin
                then foreach( l lin fprintf(fid "%s\n" l) )
                else fprintf(fid "Sin diferencias.\n"))
            close(fid)
            view(fich nil sprintf(nil "Diferencias %s" cell))
        )
        printf("Seguidor de versiones: %d diferencias -> %s\n" length(lin) fich)
    )
)

;; Etiqueta de una vista: nombre + fecha de su sch.oa; nil = estado en edicion
procedure( vtEtiqueta(lib cell v cv)
    if( v
        then sprintf(nil "%s   [%s]" v vtFecha(lib cell v))
        else sprintf(nil "(en edicion)%s"
            if( cv~>modifiedButNotSaved " con cambios sin guardar" "")))
)

procedure( vtDiferencias()
    let( (cv lib cell view vPrev prevCv)
        cv = vtContexto()
        when( cv
            lib = cv~>libName
            cell = cv~>cellName
            view = cv~>viewName
            vPrev = vtVista(view "prev")
            if( !vtExiste(lib cell vPrev) then
                printf("Seguidor de versiones: aun no hay version anterior de %s.\n" cell)
            else
                prevCv = dbOpenCellViewByType(lib cell vPrev nil "r")
                vtEscribirInforme(lib cell
                    vtEtiqueta(lib cell vPrev cv) prevCv
                    vtEtiqueta(lib cell nil cv) cv
                    view)
                dbClose(prevCv)
            )
        )
    )
)

;; -------------------------------------------------------------------------
;; COMPARAR DOS VERSIONES CUALESQUIERA
;; -------------------------------------------------------------------------
vtCompararSel = nil
vtCompararOpc = nil

;; Vistas de la celda con sch.oa (versiones), sin la de trabajo ni la temporal:
;; primero anterior y actual, luego el resto de la mas nueva a la mas antigua
;; (las de fecha ISO se ordenan solas por nombre)
procedure( vtVistasVersion(lib cell view)
    let( (obj todas fijas resto)
        obj = ddGetObj(lib cell)
        todas = if( obj then setof(v obj~>views~>name
            v != view && v != vtVista(view "tmp") && vtRutaOa(lib cell v)) else nil)
        fijas = setof(v list(vtVista(view "prev") vtVista(view "cur")) member(v todas))
        resto = reverse(sort(setof(v todas !member(v fijas)) 'alphalessp))
        append(fijas resto)
    )
)

procedure( vtCompararVersiones()
    let( (cv lib cell view vistas labels defA form)
        cv = vtContexto()
        when( cv
            lib = cv~>libName
            cell = cv~>cellName
            view = cv~>viewName
            vistas = vtVistasVersion(lib cell view)
            if( !vistas then
                printf("Seguidor de versiones: %s aun no tiene versiones guardadas.\n" cell)
            else
                vtCompararSel = cv
                ;; alist (etiqueta vista); vista nil = estado en edicion
                vtCompararOpc = cons(list(vtEtiqueta(lib cell nil cv) nil)
                    mapcar(lambda((v) list(vtEtiqueta(lib cell v cv) v)) vistas))
                labels = mapcar('car vtCompararOpc)
                defA = cadr(labels)
                form = hiCreateAppForm(
                    ?name 'vtCompararForm
                    ?formTitle sprintf(nil "Comparar versiones de %s" cell)
                    ?fields list(
                        hiCreateCyclicField(
                            ?name 'vtCompararA
                            ?prompt "Versión A"
                            ?choices labels
                            ?value defA
                        )
                        hiCreateCyclicField(
                            ?name 'vtCompararB
                            ?prompt "Versión B"
                            ?choices labels
                            ?value car(labels)
                        )
                    )
                    ?buttonLayout 'OKCancel
                    ?callback "vtCompararVersionesCB()"
                )
                hiDisplayForm(form)
            )
        )
    )
)

procedure( vtCompararVersionesCB()
    let( (f cv lib cell eA eB vA vB cvA cvB)
        f = hiGetCurrentForm()
        cv = vtCompararSel
        lib = cv~>libName
        cell = cv~>cellName
        eA = (f->vtCompararA)~>value
        eB = (f->vtCompararB)~>value
        vA = cadr(assoc(eA vtCompararOpc))
        vB = cadr(assoc(eB vtCompararOpc))
        if( eA == eB then
            printf("Seguidor de versiones: has elegido la misma version dos veces.\n")
        else
            ;; nil = el esquematico en edicion (no se abre ni se cierra)
            cvA = if( vA then dbOpenCellViewByType(lib cell vA nil "r") else cv)
            cvB = if( vB then dbOpenCellViewByType(lib cell vB nil "r") else cv)
            vtEscribirInforme(lib cell eA cvA eB cvB
                sprintf(nil "%s_vs_%s" or(vA "edicion") or(vB "edicion")))
            when( vA dbClose(cvA) )
            when( vB dbClose(cvB) )
        )
    )
)

;; -------------------------------------------------------------------------
;; VOLVER A LA VERSION ANTERIOR (intercambio: repetirlo deshace)
;; -------------------------------------------------------------------------
procedure( vtRestaurar(cv lib cell view vPrev vCur)
    let( (vTmp edit ok ref src dst)
        vTmp = vtVista(view "tmp")
        edit = (cv~>mode != "r")
        vtBusy = t
        ok = errset(
            progn(
                ;; 1. Intercambiar instantaneas
                vtCopiar(lib cell vCur vTmp)
                vtCopiar(lib cell vPrev vCur)
                vtCopiar(lib cell vTmp vPrev)
                vtBorrar(lib cell vTmp)
                ;; 2. Soltar el bloqueo, escribir el sch.oa y recargar
                when( edit dbReopen(cv "r") )
                src = vtRutaOa(lib cell vCur)
                dst = vtRutaOa(lib cell view)
                unless( sh(sprintf(nil "cp -f '%s' '%s'" src dst))
                    error("cp fallo"))
                ref = errset(dbRefreshCellView(cv))
                when( edit dbReopen(cv "a") )
                t
            ) t)
        vtMtimes[vtClave(lib cell view)] = vtMtime(lib cell view)
        vtBusy = nil
        foreach( w hiGetWindowList() errset(hiRedraw(w)) )
        cond(
            ( ok && ref
                printf("Seguidor de versiones: %s restaurado. Repite la accion para deshacer.\n"
                    vtClave(lib cell view)) )
            ( ok
                printf("Seguidor de versiones: restaurado en disco; cierra y reabre la celda para verlo.\n") )
            ( t
                printf("Seguidor de versiones: fallo al restaurar. Las versiones estan en %s y %s.\n"
                    vPrev vCur) )
        )
    )
)

procedure( vtVolverAnterior()
    let( (cv lib cell view vPrev vCur)
        cv = vtContexto()
        when( cv
            lib = cv~>libName
            cell = cv~>cellName
            view = cv~>viewName
            vPrev = vtVista(view "prev")
            vCur = vtVista(view "cur")
            cond(
                ( cv~>modifiedButNotSaved
                    printf("Seguidor de versiones: hay cambios sin guardar. Guarda o usa File > Discard Edits antes de volver.\n") )
                ( !vtExiste(lib cell vPrev) || !vtExiste(lib cell vCur)
                    printf("Seguidor de versiones: aun no hay version anterior de %s.\n" cell) )
                ( hiDisplayAppDBox(
                      ?name 'vtConfirmDBox
                      ?dboxBanner "Seguidor de versiones"
                      ?dboxText sprintf(nil
                          "Sustituir %s por la version guardada el %s?\nLa actual pasa a ser la anterior: repetir la accion la recupera."
                          vtClave(lib cell view) vtFecha(lib cell vPrev))
                      ?buttonLayout 'YesNo)
                    vtRestaurar(cv lib cell view vPrev vCur) )
            )
        )
    )
)

procedure( vtAbrirAnterior()
    let( (cv vPrev)
        cv = vtContexto()
        when( cv
            vPrev = vtVista(cv~>viewName "prev")
            if( vtExiste(cv~>libName cv~>cellName vPrev)
                then geOpen(?lib cv~>libName ?cell cv~>cellName ?view vPrev ?mode "r")
                else printf("Seguidor de versiones: aun no hay version anterior.\n"))
        )
    )
)

;; -------------------------------------------------------------------------
;; GUARDAR VERSION ACTUAL CON NOMBRE
;; Fecha y hora en ISO 8601 formato basico (20261006T114210): sin ":" ni
;; "-", que dan problemas en nombres de vista. Nombre final:
;; <vista>_<fecha> (p.ej. schematic_20261006T114210) o el nombre especial.
;; Se copia el estado en edicion, incluidos los cambios sin guardar.
;; -------------------------------------------------------------------------
vtGuardarCv = nil

;; getCurrentTime() -> "Oct  6 11:42:10 2026" -> "20261006T114210"
procedure( vtFechaIso()
    let( (p meses mes)
        p = parseString(getCurrentTime() " ")
        meses = '("Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec")
        mes = length(meses) - length(member(car(p) meses)) + 1
        sprintf(nil "%s%02d%02dT%s" nth(3 p) mes atoi(cadr(p))
            buildString(parseString(caddr(p) ":") ""))
    )
)

procedure( vtGuardarVersion()
    let( (cv form)
        cv = vtContexto()
        when( cv
            vtGuardarCv = cv
            form = hiCreateAppForm(
                ?name 'vtGuardarForm
                ?formTitle "Guardar versión actual"
                ?fields list(
                    hiCreateRadioField(
                        ?name 'vtGuardarModo
                        ?prompt "Nombre de la vista"
                        ?choices list("Fecha y hora actual" "Nombre especial")
                        ?value "Fecha y hora actual"
                    )
                    hiCreateStringField(
                        ?name 'vtGuardarNombre
                        ?prompt "Nombre especial"
                        ?value sprintf(nil "%s_" cv~>viewName)
                    )
                )
                ?buttonLayout 'OKCancel
                ?callback "vtGuardarVersionCB()"
            )
            hiDisplayForm(form)
        )
    )
)

procedure( vtGuardarVersionCB()
    let( (f cv lib cell view modo nombre)
        f = hiGetCurrentForm()
        cv = vtGuardarCv
        lib = cv~>libName
        cell = cv~>cellName
        view = cv~>viewName
        modo = (f->vtGuardarModo)~>value
        nombre = if( modo == "Nombre especial"
            then (f->vtGuardarNombre)~>value
            else sprintf(nil "%s_%s" view vtFechaIso()))
        cond(
            ( !stringp(nombre) || !rexMatchp("^[A-Za-z0-9_]+$" nombre)
                printf("Seguidor de versiones: nombre no valido \"%L\" (solo letras, numeros y _).\n" nombre) )
            ( nombre == view
                printf("Seguidor de versiones: el nombre coincide con la vista de trabajo.\n") )
            ( vtEsInstantanea(nombre)
                printf("Seguidor de versiones: los nombres vt*__ estan reservados.\n") )
            ( vtExiste(lib cell nombre) &&
              !hiDisplayAppDBox(
                  ?name 'vtSobrescribirDBox
                  ?dboxBanner "Guardar versión actual"
                  ?dboxText sprintf(nil "La vista %s ya existe en %s/%s. Sobrescribir?" nombre lib cell)
                  ?buttonLayout 'YesNo)
                printf("Seguidor de versiones: cancelado.\n") )
            ( t
                when( vtCopiar(lib cell view nombre)
                    printf("Seguidor de versiones: version guardada como %s%s\n"
                        vtClave(lib cell nombre)
                        if( cv~>modifiedButNotSaved " (incluye cambios sin guardar)" ""))
                )
            )
        )
    )
)

;; -------------------------------------------------------------------------
;; MENU + TEMPORIZADOR
;; -------------------------------------------------------------------------

;; Quita el menu de las ventanas abiertas para que un reload lo regenere
procedure( vtQuitarMenus()
    foreach( w hiGetWindowList()
        errset(
            let( (menus resto)
                menus = hiGetBannerMenus(w)
                resto = member('MenuVersiones menus)
                when( resto
                    hiDeleteBannerMenu(w length(menus) - length(resto))
                )
            )
        )
    )
)

procedure( vtRegistrarMenu(win)
    let( (items menu)
        items = list(
            hiCreateMenuItem(
                ?name 'vtItemGuardar
                ?itemText sprintf(nil "Guardar versión actual   (%s)" vtTeclaTexto)
                ?callback "vtGuardarVersion()"
            )
            hiCreateMenuItem(
                ?name 'vtItemDiff
                ?itemText "Diferencias con la versión anterior"
                ?callback "vtDiferencias()"
            )
            hiCreateMenuItem(
                ?name 'vtItemComparar
                ?itemText "Comparar versiones..."
                ?callback "vtCompararVersiones()"
            )
            hiCreateMenuItem(
                ?name 'vtItemVolver
                ?itemText "Volver a la versión anterior"
                ?callback "vtVolverAnterior()"
            )
            hiCreateMenuItem(
                ?name 'vtItemAbrir
                ?itemText "Abrir versión anterior (lectura)"
                ?callback "vtAbrirAnterior()"
            )
        )
        menu = hiCreatePulldownMenu('MenuVersiones "Seguidor de versiones" items)
        hiInsertBannerMenu(win menu length(hiGetBannerMenus(win)))
    )
)

procedure( vtTick(@optional (gen 0))
    ;; Una llamada sin numero (temporizador de una carga anterior) se para
    when( gen == vtTimerGen
        unless( vtBusy
            errset(
                foreach( w hiGetWindowList()
                    errset(
                        let( (cv)
                            ;; geGetEditCellView solo en ventanas de esquematico:
                            ;; en el CIW, Library Manager o Maestro (que si tiene
                            ;; cellView, de tipo "maestro") lanza el warning GE-2067
                            cv = if( vtEsVentanaSch(w) then geGetEditCellView(w) else nil)
                            when( cv && cv~>cellViewType == "schematic"
                                unless( member('MenuVersiones hiGetBannerMenus(w))
                                    vtRegistrarMenu(w)
                                )
                                vtVigilar(cv)
                            )
                        )
                    )
                )
            )
        )
        hiRegTimer(sprintf(nil "vtTick(%d)" gen) vtPeriodo)
    )
)

;; Cada carga incrementa vtTimerGen: el temporizador de una carga anterior
;; se para solo, asi recargar el archivo no acumula temporizadores
vtTimerGen = if( boundp('vtTimerGen) && integerp(vtTimerGen) then vtTimerGen + 1 else 1)

;; Atajo de teclado en el editor de esquematicos. El dialogo se abre con
;; "Fecha y hora actual" marcado, asi que basta con pulsar Intro.
;; Prefijos de bindkeys del editor de esquematicos registrados ahora mismo.
;; "Schematics" cubre el editor L; "Schematics XL" solo existe con licencia XL.
procedure( vtAppsEsquematico()
    let( (r reg)
        r = if( fboundp('hiGetBindKeyPrefixList) then errset(hiGetBindKeyPrefixList()) else nil)
        reg = if( r then car(r) else nil)
        if( reg
            then setof(a '("Schematics" "Schematics XL") member(a reg))
            else '("Schematics"))
    )
)

;; Si la tecla ya tiene otra funcion asignada no la pisa: avisa en el CIW
;; para que elijas otra en vtTeclaGuardar
procedure( vtRegistrarTecla()
    let( (actual r)
        ;; Solo los prefijos que existen en esta instalacion: consultar uno
        ;; no registrado (p.ej. "Schematics L") imprime un WARNING en el CIW
        foreach( app vtAppsEsquematico()
            r = errset(hiGetBindKey(app vtTeclaGuardar))
            actual = if( r then car(r) else nil)
            if( stringp(actual) && actual != "" && actual != "vtGuardarVersion()" then
                printf("Seguidor de versiones: %s ya ejecuta %s en %s; no se reasigna. Cambia vtTeclaGuardar.\n"
                    vtTeclaTexto actual app)
            else
                errset(hiSetBindKey(app vtTeclaGuardar "vtGuardarVersion()"))
            )
        )
    )
)

vtRegistrarTecla()
vtQuitarMenus()
vtTick(vtTimerGen)