From e688b306e95067b64b8966faf53ef5eeb4b1eea3 Mon Sep 17 00:00:00 2001 From: breck7 Date: Tue, 3 Sep 2024 21:10:19 +0000 Subject: [PATCH] =?UTF-8?q?Deploying=20to=20wws=20from=20@=20breck7/Cancer?= =?UTF-8?q?DB@900d260b65a13d70f7becec0069ffae9232ee799=20=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 404.html | 6 +- add.html | 526 +- blog/cancer-heatmaps.html | 4 +- blog/cat-food.html | 4 +- blog/commercials.html | 4 +- blog/feed.xml | 2 +- blog/funQuiz.html | 4 +- blog/fund.html | 4 +- blog/hawaii-cancer-moonshot.html | 4 +- blog/holey-moley.html | 4 +- blog/humor.html | 4 +- blog/index.html | 4 +- blog/moonbathing.html | 4 +- blog/natureCalls.html | 4 +- blog/prostateExams.html | 4 +- blog/sayCheese.html | 4 +- codeMirror.css | 521 + concepts/3d-crt.html | 4 +- concepts/a-lucky-man-a-memoir.html | 4 +- concepts/a-new-war-on-cancer-book.html | 4 +- concepts/aaccr-et.html | 4 +- concepts/aaci.html | 4 +- concepts/aacr.html | 4 +- concepts/aad.html | 4 +- concepts/abbott.html | 4 +- concepts/abbvie.html | 4 +- concepts/abramson-cancer-center.html | 4 +- concepts/abraxane.html | 4 +- concepts/acd-au.html | 4 +- concepts/acinar-cell-carcinoma-nos.html | 4 +- ...acinar-cell-carcinoma-of-the-pancreas.html | 4 +- concepts/acinic-cell-carcinoma.html | 4 +- concepts/acral-melanoma.html | 4 +- concepts/acs-can.html | 4 +- concepts/acs-cancer-programs.html | 4 +- concepts/acs.html | 4 +- concepts/actcr-au.html | 4 +- concepts/actinomycin.html | 4 +- concepts/activated-b-cell-type.html | 4 +- concepts/acupressure.html | 4 +- concepts/acupuncture.html | 4 +- concepts/acute-basophilic-leukemia.html | 4 +- .../acute-leukemias-of-ambiguous-lineage.html | 4 +- concepts/acute-megakaryoblastic-leukemia.html | 4 +- .../acute-monoblastic-monocytic-leukemia.html | 4 +- concepts/acute-myeloid-leukemia.html | 4 +- concepts/acute-myelomonocytic-leukemia.html | 4 +- .../acute-panmyelosis-with-myelofibrosis.html | 4 +- concepts/acute-undifferentiated-leukemia.html | 4 +- concepts/adamantinoma.html | 4 +- concepts/adenocarcinoma-in-situ.html | 4 +- concepts/adenocarcinoma-nos.html | 4 +- ...noma-of-the-gastroesophageal-junction.html | 4 +- concepts/adenoid-cystic-breast-cancer.html | 4 +- .../adenoid-cystic-carcinoma-of-the-lung.html | 4 +- concepts/adenoid-cystic-carcinoma.html | 4 +- .../adenomyoepithelioma-of-the-breast.html | 4 +- ...squamous-carcinoma-of-the-gallbladder.html | 4 +- ...enosquamous-carcinoma-of-the-pancreas.html | 4 +- ...denosquamous-carcinoma-of-the-stomach.html | 4 +- ...adenosquamous-carcinoma-of-the-tongue.html | 4 +- concepts/adrenal-gland.html | 4 +- concepts/adrenalectomy.html | 4 +- concepts/adrenocortical-adenoma.html | 4 +- concepts/adrenocortical-carcinoma.html | 4 +- concepts/adult-t-cell-leukemia-lymphoma.html | 4 +- concepts/advent-health-orlando.html | 4 +- concepts/aecc.html | 4 +- concepts/aggressive-angiomyxoma.html | 4 +- ...sive-digital-papillary-adenocarcinoma.html | 4 +- concepts/aggressive-nk-cell-leukemia.html | 4 +- .../aggressive-systemic-mastocytosis.html | 4 +- concepts/aicr.html | 4 +- concepts/albert-b-chandler-hospital.html | 4 +- concepts/alitretinoin.html | 4 +- .../alk-positive-large-b-cell-lymphoma.html | 4 +- concepts/alkaline-diet.html | 4 +- concepts/all.html | 4 +- concepts/alpha-heavy-chain-disease.html | 4 +- concepts/altretamine.html | 4 +- concepts/alveolar-rhabdomyosarcoma.html | 4 +- concepts/alveolar-soft-part-sarcoma.html | 4 +- concepts/alvin-j-siteman-cancer-center.html | 4 +- concepts/american-cancer-society.html | 4 +- concepts/american-liver-foundation.html | 4 +- concepts/american-lung-association.html | 4 +- concepts/amgen.html | 4 +- ...blastic-with-t122p13.3q13.3rbm15-mkl1.html | 4 +- concepts/aml-nos.html | 4 +- concepts/aml-with-bcr-abl1.html | 4 +- ...aml-with-biallelic-mutations-of-cebpa.html | 4 +- ...16p13.1q22-or-t1616p13.1q22cbfb-myh11.html | 4 +- ...1.3q26.2-or-t33q21.3q26.2-gata2-mecom.html | 4 +- concepts/aml-with-maturation.html | 4 +- .../aml-with-minimal-differentiation.html | 4 +- concepts/aml-with-mutated-npm1.html | 4 +- concepts/aml-with-mutated-runx1.html | 4 +- ...l-with-myelodysplasia-related-changes.html | 4 +- ...-with-recurrent-genetic-abnormalities.html | 4 +- concepts/aml-with-t69p23q34.1dek-nup214.html | 4 +- .../aml-with-t821q22q22.1runx1-runx1t1.html | 4 +- .../aml-with-t911p21.3q23.3mllt3-kmt2a.html | 4 +- concepts/aml-without-maturation.html | 4 +- concepts/ampulla-of-vater.html | 4 +- concepts/ampullary-carcinoma.html | 4 +- concepts/amyloidosis.html | 4 +- concepts/anal-gland-adenocarcinoma.html | 4 +- concepts/anal-squamous-cell-carcinoma.html | 4 +- concepts/anaplastic-astrocytoma.html | 4 +- concepts/anaplastic-ependymoma.html | 4 +- concepts/anaplastic-ganglioglioma.html | 4 +- ...stic-large-cell-lymphoma-alk-negative.html | 4 +- ...stic-large-cell-lymphoma-alk-positive.html | 4 +- concepts/anaplastic-large-cell-lymphoma.html | 4 +- concepts/anaplastic-meningioma.html | 4 +- concepts/anaplastic-oligoastrocytoma.html | 4 +- concepts/anaplastic-oligodendroglioma.html | 4 +- ...plastic-pleomorphic-xanthoastrocytoma.html | 4 +- concepts/anaplastic-thyroid-cancer.html | 4 +- concepts/angiocentric-glioma.html | 4 +- .../angioimmunoblastic-t-cell-lymphoma.html | 4 +- .../angiomatoid-fibrous-histiocytoma.html | 4 +- concepts/angiosarcoma.html | 4 +- concepts/annals-of-oncology.html | 4 +- concepts/anorectal-mucosal-melanoma.html | 4 +- concepts/anticancer.html | 4 +- concepts/apl-with-pml-rara.html | 4 +- concepts/appendiceal-adenocarcinoma.html | 4 +- concepts/argentina-ncc.html | 4 +- concepts/arizona-cancer-center.html | 4 +- concepts/aromatherapy.html | 4 +- concepts/arpha-h.html | 4 +- concepts/asco.html | 4 +- concepts/astrazeneca.html | 4 +- concepts/astroblastoma.html | 4 +- concepts/astrocytoma.html | 4 +- concepts/atezolizumab.html | 4 +- .../atypical-choroid-plexus-papilloma.html | 4 +- ...al-chronic-myeloid-leukemia-bcr-abl1-.html | 4 +- concepts/atypical-fibroxanthoma.html | 4 +- concepts/atypical-lung-carcinoid.html | 4 +- concepts/atypical-meningioma.html | 4 +- concepts/atypical-nevus.html | 4 +- concepts/atypical-pituitary-adenoma.html | 4 +- .../atypical-teratoid-rhabdoid-tumor.html | 4 +- concepts/azacitidine.html | 4 +- concepts/azathioprine.html | 4 +- ...-dlbcl-and-classical-hodgkin-lymphoma.html | 4 +- concepts/b-cell-prolymphocytic-leukemia.html | 4 +- ...astic-leukemia-lymphoma-bcr-abl1-like.html | 4 +- ...b-lymphoblastic-leukemia-lymphoma-nos.html | 4 +- ...-leukemia-lymphoma-with-hyperdiploidy.html | 4 +- ...c-leukemia-lymphoma-with-hypodiploidy.html | 4 +- ...blastic-leukemia-lymphoma-with-iamp21.html | 4 +- ...-with-recurrent-genetic-abnormalities.html | 4 +- ...a-lymphoma-with-t119q23p13.3tcf3-pbx1.html | 4 +- ...phoma-with-t1221p13.2q22.1-etv6-runx1.html | 4 +- ...-lymphoma-with-t514q31.1q32.3-il3-igh.html | 4 +- ...-lymphoma-with-t922q34.1q11.2bcr-abl1.html | 4 +- ...mphoma-with-tv11q23.3kmt2a-rearranged.html | 4 +- .../b-lymphoblastic-leukemia-lymphoma.html | 4 +- concepts/basal-cell-carcinoma.html | 4 +- ...loid-large-cell-carcinoma-of-the-lung.html | 4 +- ...saloid-penile-squamous-cell-carcinoma.html | 4 +- concepts/baxter.html | 4 +- concepts/bayer.html | 4 +- concepts/bbcr-ar.html | 4 +- concepts/bccr-bn.html | 4 +- concepts/bcrisktool.html | 4 +- concepts/becton-dickinson-and-company.html | 4 +- concepts/beigene.html | 4 +- ...-medicine-and-what-matters-in-the-end.html | 4 +- concepts/belotecan.html | 4 +- concepts/bendamustine.html | 4 +- .../benign-phyllodes-tumor-of-the-breast.html | 4 +- concepts/beth-israel.html | 4 +- .../betrayed-by-nature-the-war-on-cancer.html | 4 +- concepts/bevacizumab.html | 4 +- concepts/bexarotene.html | 4 +- concepts/biliary-tract.html | 4 +- concepts/biogen.html | 4 +- concepts/biontech.html | 4 +- concepts/bladder-adenocarcinoma.html | 4 +- concepts/bladder-cancer-subreddit.html | 4 +- concepts/bladder-cancer.html | 4 +- concepts/bladder-squamous-cell-carcinoma.html | 4 +- concepts/bladder-urothelial-carcinoma.html | 4 +- ...-plasmacytoid-dendritic-cell-neoplasm.html | 4 +- concepts/bleomycin.html | 4 +- concepts/bncr-bt.html | 4 +- concepts/boehringer-ingelheim.html | 4 +- .../bone-marrow-and-cancer-foundation.html | 4 +- concepts/bone.html | 4 +- ...derline-phyllodes-tumor-of-the-breast.html | 4 +- concepts/bortezomib.html | 4 +- concepts/brachytherapy.html | 4 +- concepts/brain.html | 4 +- concepts/breast-angiosarcoma.html | 4 +- concepts/breast-book.html | 4 +- concepts/breast-cancer-awareness-month.html | 4 +- concepts/breast-cancer-subreddit.html | 4 +- .../breast-carcinoma-with-signet-ring.html | 4 +- concepts/breast-ductal-carcinoma-in-situ.html | 4 +- .../breast-fibroepithelial-neoplasms.html | 4 +- ...ciated-anaplastic-large-cell-lymphoma.html | 4 +- concepts/breast-invasive-cancer-nos.html | 4 +- concepts/breast-invasive-carcinoma-nos.html | 4 +- .../breast-invasive-carcinosarcoma-nos.html | 4 +- .../breast-invasive-ductal-carcinoma.html | 4 +- .../breast-invasive-lobular-carcinoma.html | 4 +- ...ast-invasive-mixed-mucinous-carcinoma.html | 4 +- .../breast-lobular-carcinoma-in-situ.html | 4 +- ...st-mixed-ductal-and-lobular-carcinoma.html | 4 +- concepts/breast-neoplasm-nos.html | 4 +- concepts/breast-prostheses.html | 4 +- concepts/breast-sarcoma.html | 4 +- concepts/breast.html | 4 +- concepts/breastcancer-dot-org.html | 4 +- concepts/brenner-tumor-benign.html | 4 +- concepts/brenner-tumor-borderline.html | 4 +- concepts/brenner-tumor-malignant.html | 4 +- concepts/brenner-tumor.html | 4 +- concepts/bristol-myers-squibb.html | 4 +- ...itt-like-lymphoma-with-11q-aberration.html | 4 +- concepts/burkitt-lymphoma.html | 4 +- concepts/busulfan.html | 4 +- concepts/cabazitaxel.html | 4 +- concepts/cabozantinib.html | 4 +- concepts/camptothecin.html | 4 +- concepts/canada-ccm.html | 4 +- concepts/canada-ohcc.html | 4 +- concepts/canadian-cancer-society.html | 4 +- .../canadian-partnership-against-cancer.html | 4 +- concepts/cancer-australia.html | 4 +- concepts/cancer-cell-journal.html | 4 +- concepts/cancer-council-australia.html | 4 +- concepts/cancer-crackdown.html | 4 +- ...d-victory-over-the-war-on-cancer-book.html | 4 +- concepts/cancer-discovery.html | 4 +- concepts/cancer-dot-net.html | 4 +- concepts/cancer-is-not-a-disease-book.html | 4 +- concepts/cancer-journal.html | 4 +- concepts/cancer-of-unknown-primary-nos.html | 4 +- concepts/cancer-of-unknown-primary.html | 4 +- concepts/cancer-research-journal.html | 4 +- concepts/cancer-research-uk.html | 4 +- concepts/cancer-risk-calculator.html | 4 +- concepts/cancer-society-of-finland.html | 4 +- concepts/cancer-statistics-center.html | 4 +- concepts/cancer-subreddit.html | 4 +- concepts/cancer-support-community.html | 4 +- .../cancer-the-emperor-of-all-maladies.html | 4 +- concepts/cancer-today.html | 4 +- concepts/cancer-ward.html | 4 +- concepts/cancerbuddy.html | 4 +- concepts/cancercare.html | 4 +- concepts/cancerdb.html | 4 +- concepts/cancerfonden.html | 4 +- concepts/canswer.html | 4 +- concepts/capecitabine.html | 4 +- concepts/carboplatin.html | 4 +- concepts/carboquone.html | 4 +- .../carcinoma-with-chondroid-metaplasia.html | 4 +- .../carcinoma-with-osseous-metaplasia.html | 4 +- concepts/carmustine.html | 4 +- .../case-comprehensive-cancer-center.html | 4 +- concepts/cbioportal.html | 4 +- concepts/ccan.html | 4 +- concepts/ccdi-annual-symposium.html | 4 +- concepts/ccdi.html | 4 +- concepts/ccr-ar.html | 4 +- concepts/ccrcal.html | 4 +- concepts/ccrisktool.html | 4 +- concepts/ccrn-ng.html | 4 +- ...sion-of-cancer-prevention-and-control.html | 4 +- concepts/cdc.html | 4 +- concepts/cedars-sinai.html | 4 +- concepts/cellular-schwannoma.html | 4 +- concepts/center-for-molecular-oncology.html | 4 +- concepts/central-neurocytoma.html | 4 +- concepts/cerebellar-liponeurocytoma.html | 4 +- concepts/cervical-adenocarcinoma-in-situ.html | 4 +- concepts/cervical-adenocarcinoma.html | 4 +- .../cervical-adenoid-basal-carcinoma.html | 4 +- .../cervical-adenoid-cystic-carcinoma.html | 4 +- .../cervical-adenosquamous-carcinoma.html | 4 +- concepts/cervical-cancer-subreddit.html | 4 +- concepts/cervical-clear-cell-carcinoma.html | 4 +- concepts/cervical-endometrioid-carcinoma.html | 4 +- concepts/cervical-leiomyosarcoma.html | 4 +- concepts/cervical-neuroendocrine-tumor.html | 4 +- concepts/cervical-rhabdomyosarcoma.html | 4 +- concepts/cervical-serous-carcinoma.html | 4 +- .../cervical-squamous-cell-carcinoma.html | 4 +- concepts/cervix.html | 4 +- ...ao-family-comprehensive-cancer-center.html | 4 +- ...ncers-theories-concerning-carcinogene.html | 4 +- concepts/chlorambucil.html | 4 +- concepts/chlormethine.html | 4 +- concepts/chlorozotocin.html | 4 +- concepts/cholangiocarcinoma.html | 4 +- concepts/chondroblastic-osteosarcoma.html | 4 +- concepts/chondroblastoma.html | 4 +- concepts/chondrosarcoma.html | 4 +- ...hordoid-glioma-of-the-third-ventricle.html | 4 +- concepts/chordoid-meningioma.html | 4 +- concepts/chordoma.html | 4 +- concepts/choriocarcinoma-testis.html | 4 +- concepts/choriocarcinoma-uterus.html | 4 +- concepts/choriocarcinoma.html | 4 +- concepts/choroid-plexus-carcinoma.html | 4 +- concepts/choroid-plexus-papilloma.html | 4 +- concepts/choroid-plexus-tumor.html | 4 +- concepts/chris-beat-cancer.html | 4 +- .../chromophobe-renal-cell-carcinoma.html | 4 +- .../chronic-eosinophilic-leukemia-nos.html | 4 +- ...c-leukemia-small-lymphocytic-lymphoma.html | 4 +- ...phoproliferative-disorder-of-nk-cells.html | 4 +- concepts/chronic-myelogenous-leukemia.html | 4 +- .../chronic-myeloid-leukemia-bcr-abl1p.html | 4 +- .../chronic-myelomonocytic-leukemia-0.html | 4 +- .../chronic-myelomonocytic-leukemia-1.html | 4 +- .../chronic-myelomonocytic-leukemia-2.html | 4 +- concepts/chronic-myelomonocytic-leukemia.html | 4 +- concepts/chronic-neutrophilic-leukemia.html | 4 +- ...conodular-papillary-tumor-of-the-lung.html | 4 +- concepts/cirm.html | 4 +- concepts/cisplatin.html | 4 +- ...y-of-hope-comprehensive-cancer-center.html | 4 +- ...-patient-and-family-education-library.html | 4 +- concepts/classical-hodgkin-lymphoma-ptld.html | 4 +- concepts/classical-hodgkin-lymphoma.html | 4 +- .../clear-cell-borderline-ovarian-tumor.html | 4 +- .../clear-cell-carcinoma-of-the-lung.html | 4 +- concepts/clear-cell-ependymoma.html | 4 +- concepts/clear-cell-meningioma.html | 4 +- .../clear-cell-odontogenic-carcinoma.html | 4 +- concepts/clear-cell-ovarian-cancer.html | 4 +- ...r-cell-papillary-renal-cell-carcinoma.html | 4 +- concepts/clear-cell-sarcoma-of-kidney.html | 4 +- concepts/clear-cell-sarcoma.html | 4 +- concepts/cleveland-clinic.html | 4 +- concepts/clinical-cancer-research.html | 4 +- concepts/clinical-trials-dot-gov.html | 4 +- concepts/cms.html | 4 +- concepts/cnio.html | 4 +- concepts/coc.html | 4 +- concepts/cocr-ar.html | 4 +- concepts/cognitive-behavioral-therapy.html | 4 +- ...pring-harbor-laboratory-cancer-center.html | 4 +- concepts/colectomy.html | 4 +- .../collecting-duct-renal-cell-carcinoma.html | 4 +- .../college-of-american-pathologists.html | 4 +- concepts/colon-adenocarcinoma-in-situ.html | 4 +- concepts/colon-adenocarcinoma.html | 4 +- concepts/colon-cancer-subreddit.html | 4 +- concepts/colon.html | 4 +- ...c-type-adenocarcinoma-of-the-appendix.html | 4 +- concepts/colonoscopy.html | 4 +- concepts/colorectal-adenocarcinoma.html | 4 +- concepts/colorectal.html | 4 +- .../combined-small-cell-lung-carcinoma.html | 4 +- concepts/complete-hydatidiform-mole.html | 4 +- concepts/congenital-nevus.html | 4 +- concepts/conjunctival-melanoma.html | 4 +- concepts/conventional-type-chordoma.html | 4 +- ...aniopharyngioma-adamantinomatous-type.html | 4 +- .../craniopharyngioma-papillary-type.html | 4 +- concepts/crptdf-ar.html | 4 +- concepts/crsba-dz.html | 4 +- concepts/crwb-dz.html | 4 +- concepts/cryoablation.html | 4 +- concepts/ct-colonoscopy.html | 4 +- concepts/ctca.html | 4 +- concepts/curium-pharma.html | 4 +- concepts/cutaneous-mastocytosis.html | 4 +- concepts/cutaneous-melanoma.html | 4 +- .../cutaneous-squamous-cell-carcinoma.html | 4 +- concepts/cyclophosphamide.html | 4 +- concepts/cystectomy.html | 4 +- concepts/cystic-tumor-of-the-pancreas.html | 4 +- concepts/cytarabine.html | 4 +- concepts/czechrepublic-ncc.html | 4 +- concepts/dacarbazine.html | 4 +- concepts/daiichi-sankyo.html | 4 +- ...mon-runyon-cancer-research-foundation.html | 4 +- ...-l-duncan-comprehensive-cancer-center.html | 4 +- .../dana-farber-harvard-cancer-center.html | 4 +- concepts/dana-farber.html | 4 +- concepts/dartmouth-cancer-center.html | 4 +- concepts/daunorubicin.html | 4 +- ...or-integrative-cancer-research-at-mit.html | 4 +- concepts/dedifferentiated-chondrosarcoma.html | 4 +- concepts/dedifferentiated-chordoma.html | 4 +- concepts/dedifferentiated-liposarcoma.html | 4 +- concepts/dendritic-cell-sarcoma.html | 4 +- concepts/dermatofibroma.html | 4 +- concepts/dermatofibrosarcoma-protuberans.html | 4 +- concepts/desmoid-aggressive-fibromatosis.html | 4 +- .../desmoplastic-infantile-astrocytoma.html | 4 +- .../desmoplastic-infantile-ganglioglioma.html | 4 +- concepts/desmoplastic-melanoma.html | 4 +- .../desmoplastic-nodular-medulloblastoma.html | 4 +- .../desmoplastic-small-round-cell-tumor.html | 4 +- concepts/desmoplastic-trichoepithelioma.html | 4 +- concepts/dicycloplatin.html | 4 +- concepts/diffuse-astrocytoma.html | 4 +- concepts/diffuse-glioma.html | 4 +- .../diffuse-intrinsic-pontine-glioma.html | 4 +- .../diffuse-large-b-cell-lymphoma-nos.html | 4 +- .../diffuse-type-stomach-adenocarcinoma.html | 4 +- ...disseminated-juvenile-xanthogranuloma.html | 4 +- ...-associated-with-chronic-inflammation.html | 4 +- concepts/docetaxel.html | 4 +- concepts/dominicanrepublic-ncc.html | 4 +- concepts/doxifluridine.html | 4 +- concepts/doxorubicin.html | 4 +- concepts/dr-sebi-cure-for-cancer.html | 4 +- concepts/dta-dz.html | 4 +- concepts/duke-cancer-institute.html | 4 +- concepts/duodenal-adenocarcinoma.html | 4 +- .../duodenal-type-follicular-lymphoma.html | 4 +- concepts/dutch-cancer-society.html | 4 +- ...ysembryoplastic-neuroepithelial-tumor.html | 4 +- concepts/dysgerminoma-vulva-vagina.html | 4 +- concepts/dysgerminoma.html | 4 +- ...e-cerebellum-lhermitte-duclos-disease.html | 4 +- concepts/early-detection-book.html | 4 +- ...cell-precursor-lymphoblastic-leukemia.html | 4 +- concepts/ebus.html | 4 +- concepts/ebv-positive-dlbcl-nos.html | 4 +- .../ebv-positive-mucocutaneous-ulcer.html | 4 +- concepts/egypt-ncc.html | 4 +- concepts/electroacupuncture.html | 4 +- concepts/eli-lilly-and-company.html | 4 +- concepts/elsevier.html | 4 +- concepts/embase.html | 4 +- concepts/embryonal-carcinoma-cns-brain.html | 4 +- concepts/embryonal-carcinoma-testis.html | 4 +- .../embryonal-carcinoma-vulva-vagina.html | 4 +- concepts/embryonal-carcinoma.html | 4 +- concepts/embryonal-rhabdomyosarcoma.html | 4 +- ...h-abundant-neuropil-and-true-rosettes.html | 4 +- concepts/embryonal-tumor.html | 4 +- concepts/emerging-med.html | 4 +- concepts/emperor-of-all-maladies.html | 4 +- concepts/encapsulated-glioma.html | 4 +- concepts/encr-sw.html | 4 +- concepts/endocervical-adenocarcinoma.html | 4 +- ...mucin-producing-sweat-gland-carcinoma.html | 4 +- concepts/endometrial-carcinoma.html | 4 +- concepts/endometrial-stromal-sarcoma.html | 4 +- .../endometrioid-borderlin-ovarian-tumor.html | 4 +- concepts/endometrioid-ovarian-cancer.html | 4 +- ...nteropathy-associated-t-cell-lymphoma.html | 4 +- concepts/ependymoma.html | 4 +- concepts/ependymomal-tumor.html | 4 +- concepts/epirubicin.html | 4 +- .../epithelial-myoepithelial-carcinoma.html | 4 +- ...helial-type-metaplastic-breast-cancer.html | 4 +- .../epithelioid-hemangioendothelioma.html | 4 +- concepts/epithelioid-sarcoma.html | 4 +- concepts/epithelioid-trophoblastic-tumor.html | 4 +- concepts/ercr-ar.html | 4 +- concepts/erdheim-chester-disease.html | 4 +- concepts/erlotinib.html | 4 +- concepts/esophageal-adenocarcinoma.html | 4 +- ...ageal-poorly-differentiated-carcinoma.html | 4 +- .../esophageal-squamous-cell-carcinoma.html | 4 +- concepts/esophagectomy.html | 4 +- concepts/esophagogastric-adenocarcinoma.html | 4 +- concepts/esophagus-stomach.html | 4 +- ...sential-thrombocythemia-myelofibrosis.html | 4 +- concepts/essential-thrombocythemia.html | 4 +- concepts/etoposide.html | 4 +- concepts/europa-donna-armenia.html | 4 +- concepts/ewing-sarcoma-of-soft-tissue.html | 4 +- concepts/ewing-sarcoma.html | 4 +- concepts/exatecan.html | 4 +- concepts/exercise.html | 4 +- concepts/extra-gonadal-germ-cell-tumor.html | 4 +- concepts/extrahepatic-cholangiocarcinoma.html | 4 +- concepts/extramammary-paget-disease.html | 4 +- ...ociated-lymphoid-tissue-malt-lymphoma.html | 4 +- ...anodal-nk--t-cell-lymphoma-nasal-type.html | 4 +- concepts/extraosseous-plasmacytoma.html | 4 +- .../extraskeletal-myxoid-chondrosarcoma.html | 4 +- concepts/extraventricular-neurocytoma.html | 4 +- concepts/eye.html | 4 +- concepts/fda.html | 4 +- concepts/fecal-immunochemical-test.html | 4 +- .../fh-deficient-renal-cell-carcinoma.html | 4 +- concepts/fibroadenoma.html | 4 +- concepts/fibroblastic-osteosarcoma.html | 4 +- .../fibroblastic-reticular-cell-tumor.html | 4 +- concepts/fibrolamellar-carcinoma.html | 4 +- concepts/fibrosarcoma.html | 4 +- concepts/fibrothecoma.html | 4 +- .../florid-follicular-hyperplasia-ptld.html | 4 +- concepts/fluorouracil.html | 4 +- .../follicular-dendritic-cell-sarcoma.html | 4 +- concepts/follicular-lymphoma.html | 4 +- concepts/follicular-t-cell-lymphoma.html | 4 +- concepts/follicular-thyroid-cancer.html | 4 +- concepts/fotemustine.html | 4 +- concepts/fox-chase-cancer-center.html | 4 +- ...fred-and-pamela-buffett-cancer-center.html | 4 +- concepts/fred-hutch.html | 4 +- ...rsity-of-washington-cancer-consortium.html | 4 +- concepts/free-diving.html | 4 +- .../french-national-cancer-institute.html | 4 +- concepts/gallbladder-adenocarcinoma-nos.html | 4 +- concepts/gallbladder-cancer.html | 4 +- concepts/gamma-heavy-chain-disease.html | 4 +- concepts/gangliocytoma.html | 4 +- concepts/ganglioglioma.html | 4 +- concepts/ganglioneuroblastoma.html | 4 +- concepts/ganglioneuroma.html | 4 +- concepts/gastrectomy.html | 4 +- concepts/gastric-remnant-adenocarcinoma.html | 4 +- concepts/gastric-type-mucinous-carcinoma.html | 4 +- ...crine-tumors-of-the-esophagus-stomach.html | 4 +- ...astrointestinal-neuroendocrine-tumors.html | 4 +- concepts/gastrointestinal-stromal-tumor.html | 4 +- concepts/gco.html | 4 +- concepts/gcr-ug.html | 4 +- concepts/gdc.html | 4 +- concepts/gefitinib.html | 4 +- concepts/gemcitabine.html | 4 +- concepts/genentech.html | 4 +- ...-lombardi-comprehensive-cancer-center.html | 4 +- concepts/georgia-ncc.html | 4 +- concepts/germ-cell-tumor-brain.html | 4 +- concepts/germ-cell-tumor-of-the-vulva.html | 4 +- ...ll-tumor-with-somatic-type-malignancy.html | 4 +- concepts/germany-ncc.html | 4 +- concepts/germinal-center-b-cell-type.html | 4 +- concepts/germinoma.html | 4 +- .../gestational-trophoblastic-disease.html | 4 +- concepts/gfobt.html | 4 +- .../giant-cell-carcinoma-of-the-lung.html | 4 +- concepts/giant-cell-tumor-of-bone.html | 4 +- concepts/gideon-burrows-dot-com.html | 4 +- concepts/gilead-sciences.html | 4 +- .../glassy-cell-carcinoma-of-the-cervix.html | 4 +- concepts/glioblastoma-multiforme.html | 4 +- concepts/glioblastoma.html | 4 +- concepts/glioma-nos.html | 4 +- concepts/gliosarcoma.html | 4 +- concepts/glomangiosarcoma.html | 4 +- ...goblet-cell-carcinoid-of-the-appendix.html | 4 +- concepts/gonadoblastoma.html | 4 +- concepts/granular-cell-tumor.html | 4 +- concepts/granulosa-cell-tumor.html | 4 +- concepts/gsk.html | 4 +- concepts/hairy-cell-leukemia-variant.html | 4 +- concepts/hairy-cell-leukemia.html | 4 +- ...c-simmons-comprehensive-cancer-center.html | 4 +- concepts/hawaii-tumor-registry.html | 4 +- concepts/head-and-neck-carcinoma-other.html | 4 +- concepts/head-and-neck-mucosal-melanoma.html | 4 +- ...ead-and-neck-neuroendocrine-carcinoma.html | 4 +- ...ous-cell-carcinoma-of-unknown-primary.html | 4 +- ...head-and-neck-squamous-cell-carcinoma.html | 4 +- concepts/head-and-neck.html | 4 +- concepts/healing-strong.html | 4 +- concepts/hemangioblastoma.html | 4 +- concepts/hemangioma.html | 4 +- ...icytoma-of-the-central-nervous-system.html | 4 +- concepts/hepatectomy.html | 4 +- concepts/hepatic-arterial-infusion.html | 4 +- concepts/hepatoblastoma.html | 4 +- concepts/hepatocellular-adenoma.html | 4 +- ...-plus-intrahepatic-cholangiocarcinoma.html | 4 +- concepts/hepatocellular-carcinoma.html | 4 +- concepts/hepatosplenic-t-cell-lymphoma.html | 4 +- ...rt-irving-comprehensive-cancer-center.html | 4 +- concepts/hhs.html | 4 +- concepts/hhv8-positive-dlbcl-nos.html | 4 +- concepts/high-grade-b-cell-lymphoma-nos.html | 4 +- ...c-and-bcl2-and-or-bcl6-rearrangements.html | 4 +- ...igh-grade-endometrial-stromal-sarcoma.html | 4 +- concepts/high-grade-glioma-nos.html | 4 +- ...ine-carcinoma-of-the-colon-and-rectum.html | 4 +- ...oendocrine-carcinoma-of-the-esophagus.html | 4 +- ...neuroendocrine-carcinoma-of-the-ovary.html | 4 +- ...uroendocrine-carcinoma-of-the-stomach.html | 4 +- .../high-grade-neuroepithelial-tumor.html | 4 +- ...gh-grade-serous-fallopian-tube-cancer.html | 4 +- .../high-grade-serous-ovarian-cancer.html | 4 +- concepts/high-grade-surface-osteosarcoma.html | 4 +- ...tiocytic-and-dendritic-cell-neoplasms.html | 4 +- .../histiocytic-dendritic-cell-sarcoma.html | 4 +- concepts/histiocytic-sarcoma.html | 4 +- concepts/hodgkin-lymphoma.html | 4 +- .../holden-comprehensive-cancer-center.html | 4 +- concepts/hollings-cancer-center.html | 4 +- concepts/hopa.html | 4 +- concepts/hope4cancer.html | 4 +- concepts/houston-methodist.html | 4 +- concepts/how-to-starve-cancer-book.html | 4 +- concepts/hungary-ncc.html | 4 +- concepts/huntsman-cancer-institute.html | 4 +- concepts/hurthle-cell-thyroid-cancer.html | 4 +- ...ing-trabecular-adenoma-of-the-thyroid.html | 4 +- ...rme-like-lymphoproliferative-disorder.html | 4 +- concepts/hydroxyurea.html | 4 +- concepts/hypnosis.html | 4 +- .../hypopharynx-squamous-cell-carcinoma.html | 4 +- concepts/hysterectomy.html | 4 +- concepts/iacc-ao.html | 4 +- concepts/iaea.html | 4 +- concepts/iarc.html | 4 +- concepts/ibcr-ng.html | 4 +- concepts/ican.html | 4 +- concepts/icd-10-pcs.html | 4 +- concepts/icelandic-cancer-society.html | 4 +- concepts/idarubicin.html | 4 +- concepts/ifosfamide.html | 4 +- concepts/iga.html | 4 +- concepts/igg.html | 4 +- concepts/igm.html | 4 +- concepts/imatinib.html | 4 +- concepts/imerman-angels.html | 4 +- concepts/immature-teratoma-cns-brain.html | 4 +- concepts/immature-teratoma-vulva-vagina.html | 4 +- concepts/immature-teratoma.html | 4 +- concepts/immunotherapy.html | 4 +- concepts/imrt.html | 4 +- concepts/in-situ-follicular-neoplasia.html | 4 +- concepts/in-situ-mantle-cell-neoplasia.html | 4 +- concepts/incan.html | 4 +- .../indeterminate-dendritic-cell-tumor.html | 4 +- concepts/index-medicus.html | 4 +- concepts/india-nci.html | 4 +- ...ren-simon-comprehensive-cancer-center.html | 4 +- concepts/indolent-systemic-mastocytosis.html | 4 +- ...roliferative-disorder-of-the-gi-tract.html | 4 +- concepts/infantile-fibrosarcoma.html | 4 +- concepts/infectious-mononucleosis-ptld.html | 4 +- concepts/inflammatory-breast-cancer.html | 4 +- ...mmatory-myofibroblastic-bladder-tumor.html | 4 +- ...flammatory-myofibroblastic-lung-tumor.html | 4 +- .../inflammatory-myofibroblastic-tumor.html | 4 +- ...nterdigitating-dendritic-cell-sarcoma.html | 4 +- concepts/intestinal-ampullary-carcinoma.html | 4 +- .../intestinal-type-mucinous-carcinoma.html | 4 +- ...ntestinal-type-stomach-adenocarcinoma.html | 4 +- concepts/intimal-sarcoma.html | 4 +- ...traductal-papillary-mucinous-neoplasm.html | 4 +- concepts/intrahepatic-cholangiocarcinoma.html | 4 +- concepts/intramuscular-injection.html | 4 +- .../intravascular-large-b-cell-lymphoma.html | 4 +- concepts/intravenous-therapy.html | 4 +- concepts/intravesical-therapy.html | 4 +- concepts/invasive-breast-carcinoma.html | 4 +- concepts/invasive-hydatidiform-mole.html | 4 +- concepts/inverted-urothelial-papilloma.html | 4 +- concepts/iran-ncc.html | 4 +- concepts/ireland-nrg.html | 4 +- concepts/irinotecan.html | 4 +- concepts/ixabepilone.html | 4 +- concepts/johnson-and-johnson.html | 4 +- .../jonsson-comprehensive-cancer-center.html | 4 +- concepts/jordan-ncc.html | 4 +- concepts/journal-of-clinical-oncology.html | 4 +- ...rnal-of-the-national-cancer-institute.html | 4 +- .../juvenile-myelomonocytic-leukemia.html | 4 +- ...ile-secretory-carcinoma-of-the-breast.html | 4 +- concepts/kaposi-sarcoma.html | 4 +- concepts/kazakhstan-ncc.html | 4 +- concepts/kcr-ug.html | 4 +- concepts/kecr-kn.html | 4 +- concepts/kegg.html | 4 +- concepts/kidney-cancer-subreddit.html | 4 +- concepts/kidney.html | 4 +- concepts/kncr-kn.html | 4 +- concepts/knight-cancer-institute.html | 4 +- concepts/kom-op-tegen-kanker.html | 4 +- concepts/kscr-gh.html | 4 +- concepts/langerhans-cell-histiocytosis.html | 4 +- concepts/langerhans-cell-sarcoma.html | 4 +- ...cell-lymphoma-with-irf4-rearrangement.html | 4 +- ...large-cell-anaplastic-medulloblastoma.html | 4 +- ...ung-carcinoma-with-rhabdoid-phenotype.html | 4 +- concepts/large-cell-lung-carcinoma.html | 4 +- .../large-cell-neuroendocrine-carcinoma.html | 4 +- concepts/larotaxel.html | 4 +- concepts/laryngeal-cancer.html | 4 +- concepts/laryngectomy.html | 4 +- concepts/larynx-squamous-cell-carcinoma.html | 4 +- ...r-cancer-center-at-nyu-langone-health.html | 4 +- concepts/leiomyoma.html | 4 +- concepts/leiomyosarcoma.html | 4 +- concepts/lentigo-maligna-melanoma.html | 4 +- concepts/lenvatinib.html | 4 +- concepts/leukapheresis.html | 4 +- concepts/leukemia-and-lymphoma-society.html | 4 +- concepts/leukemia.html | 4 +- concepts/liposarcoma.html | 4 +- concepts/lithuania-ncc.html | 4 +- concepts/liver-angiosarcoma.html | 4 +- concepts/liver-transplant.html | 4 +- concepts/liver.html | 4 +- concepts/lomustine.html | 4 +- .../low-dose-spiral-computed-tomography.html | 4 +- concepts/low-grade-central-osteosarcoma.html | 4 +- ...low-grade-endometrial-stromal-sarcoma.html | 4 +- concepts/low-grade-fibromyxoid-sarcoma.html | 4 +- concepts/low-grade-glioma-nos.html | 4 +- concepts/low-grade-neuroepithelial-tumor.html | 4 +- concepts/low-grade-serous-ovarian-cancer.html | 4 +- concepts/lumpectomy.html | 4 +- concepts/lung-adenocarcinoma-in-situ.html | 4 +- concepts/lung-adenocarcinoma.html | 4 +- concepts/lung-adenosquamous-carcinoma.html | 4 +- concepts/lung-cancer-subreddit.html | 4 +- concepts/lung-carcinoid.html | 4 +- concepts/lung-neuroendocrine-tumor.html | 4 +- concepts/lung-squamous-cell-carcinoma.html | 4 +- concepts/lung.html | 4 +- concepts/lymphedema-sleeve.html | 4 +- ...e-depleted-classical-hodgkin-lymphoma.html | 4 +- ...ocyte-rich-classical-hodgkin-lymphoma.html | 4 +- ...pithelioma-like-carcinoma-of-the-lung.html | 4 +- concepts/lymphoid-atypical.html | 4 +- concepts/lymphoid-benign.html | 4 +- concepts/lymphoid-neoplasm.html | 4 +- concepts/lymphoma-subreddit.html | 4 +- concepts/lymphoma.html | 4 +- concepts/lymphomatoid-granulomatosis.html | 4 +- concepts/lymphomatoid-papulosis.html | 4 +- concepts/lymphoplasmacytic-lymphoma.html | 4 +- concepts/male-breast-cancer.html | 4 +- concepts/malignant-glomus-tumor.html | 4 +- concepts/malignant-lymphoma.html | 4 +- ...nant-nonepithelial-tumor-of-the-liver.html | 4 +- ...lignant-peripheral-nerve-sheath-tumor.html | 4 +- ...lignant-phyllodes-tumor-of-the-breast.html | 4 +- ...malignant-rhabdoid-tumor-of-the-liver.html | 4 +- concepts/malignant-teratoma.html | 4 +- concepts/malignant-tumor.html | 4 +- ...ry-carcinoma-of-salivary-gland-origin.html | 4 +- concepts/mammogram.html | 4 +- concepts/mantle-cell-lymphoma.html | 4 +- concepts/margin-probe.html | 4 +- concepts/marginal-zone-lymphoma.html | 4 +- concepts/marijuana.html | 4 +- concepts/markey-cancer-center.html | 4 +- concepts/masonic-cancer-center.html | 4 +- concepts/massage.html | 4 +- concepts/massey-cancer-center.html | 4 +- concepts/massgeneral.html | 4 +- concepts/mast-cell-leukemia.html | 4 +- concepts/mast-cell-sarcoma.html | 4 +- concepts/mastectomy.html | 4 +- concepts/mastocytosis.html | 4 +- concepts/mature-b-cell-neoplasms.html | 4 +- concepts/mature-t-and-nk-neoplasms.html | 4 +- concepts/mature-teratoma-cns-brain.html | 4 +- concepts/mature-teratoma-vulva-vagina.html | 4 +- concepts/mature-teratoma.html | 4 +- concepts/mayo-clinic-jacksonville.html | 4 +- concepts/mayo-clinic-phoenix.html | 4 +- concepts/mayo-clinic-rochester.html | 4 +- ...ancer-center-at-ut-health-san-antonio.html | 4 +- concepts/mcr-tz.html | 4 +- concepts/mdanderson.html | 4 +- concepts/mds-mpn-unclassifiable.html | 4 +- ...-ring-sideroblasts-and-thrombocytosis.html | 4 +- concepts/mds-unclassifiable.html | 4 +- concepts/mds-with-excess-blasts-1.html | 4 +- concepts/mds-with-excess-blasts-2.html | 4 +- concepts/mds-with-excess-blasts.html | 4 +- concepts/mds-with-isolated-del5q.html | 4 +- concepts/mds-with-multilineage-dysplasia.html | 4 +- ...deroblasts-and-multilineage-dysplasia.html | 4 +- ...roblasts-and-single-lineage-dysplasia.html | 4 +- concepts/mds-with-ring-sideroblasts.html | 4 +- .../mds-with-single-lineage-dysplasia.html | 4 +- concepts/mediastinoscopy.html | 4 +- concepts/meditation.html | 4 +- concepts/medlars.html | 4 +- concepts/medline.html | 4 +- concepts/medlineplus.html | 4 +- .../medullary-carcinoma-of-the-colon.html | 4 +- concepts/medullary-thyroid-cancer.html | 4 +- ...lloblastoma-with-extensive-nodularity.html | 4 +- concepts/medulloblastoma.html | 4 +- concepts/medulloepithelioma.html | 4 +- concepts/medullomyoblastoma.html | 4 +- concepts/melanocytoma.html | 4 +- concepts/melanoma-of-unknown-primary.html | 4 +- concepts/melanoma.html | 4 +- concepts/melanotic-medulloblastoma.html | 4 +- concepts/melanotic-schwannoma.html | 4 +- concepts/melphalan-flufenamide.html | 4 +- concepts/melphalan.html | 4 +- ...emorial-sloan-kettering-cancer-center.html | 4 +- concepts/meningioma.html | 4 +- concepts/meningothelial-tumor.html | 4 +- concepts/mercaptopurine.html | 4 +- concepts/merck-and-co.html | 4 +- concepts/merck-group.html | 4 +- concepts/merkel-cell-carcinoma.html | 4 +- ...mesenchymal-chondrosarcoma-of-the-cns.html | 4 +- concepts/mesenchymal-chondrosarcoma.html | 4 +- concepts/mesonephric-carcinoma.html | 4 +- concepts/mesothelioma.html | 4 +- ...oma-with-spindle-cell-differentiation.html | 4 +- .../metaplastic-adenosquamous-carcinoma.html | 4 +- concepts/metaplastic-breast-cancer.html | 4 +- concepts/metaplastic-carcinosarcoma.html | 4 +- .../metaplastic-squamous-cell-carcinoma.html | 4 +- concepts/methotrexate.html | 4 +- concepts/miaderm-cream.html | 4 +- concepts/microcystic-adnexal-carcinoma.html | 4 +- concepts/microscope-confocal.html | 4 +- concepts/microscope-darkfield.html | 4 +- concepts/microscope-electron.html | 4 +- concepts/microscope-flourescence.html | 4 +- concepts/microscope-inverted.html | 4 +- concepts/microscope-optical.html | 4 +- concepts/microscope-phasecontrast.html | 4 +- concepts/microscope-polarizing.html | 4 +- concepts/microscope-scanningprobe.html | 4 +- concepts/microscope-stereo.html | 4 +- concepts/microscope-ultraviolet.html | 4 +- concepts/microwave-ablation.html | 4 +- concepts/miscellaneous-brain-tumor.html | 4 +- .../miscellaneous-neuroepithelial-tumor.html | 4 +- concepts/mitobronitol.html | 4 +- concepts/mitomycin.html | 4 +- concepts/mitoxantrone.html | 4 +- concepts/mixed-ampullary-carcinoma.html | 4 +- concepts/mixed-cancer-types.html | 4 +- ...ellularity-classical-hodgkin-lymphoma.html | 4 +- concepts/mixed-cervical-carcinoma.html | 4 +- concepts/mixed-germ-cell-tumor-cns-brain.html | 4 +- concepts/mixed-germ-cell-tumor-testis.html | 4 +- .../mixed-germ-cell-tumor-vulva-vagina.html | 4 +- concepts/mixed-germ-cell-tumor.html | 4 +- concepts/mixed-ovarian-carcinoma.html | 4 +- ...henotype-acute-leukemia-b-myeloid-nos.html | 4 +- ...henotype-acute-leukemia-t-myeloid-nos.html | 4 +- ...leukemia-with-t922q34.1q11.2-bcr-abl1.html | 4 +- ...kemia-with-tv11q23.3-kmt2a-rearranged.html | 4 +- .../mixed-type-metaplastic-breast-cancer.html | 4 +- concepts/mmcr-mo.html | 4 +- concepts/mncr-ma.html | 4 +- concepts/moderna.html | 4 +- concepts/moffitt-cancer-center.html | 4 +- concepts/molar-pregnancy.html | 4 +- concepts/monoclonal-b-cell-lymphocytosis.html | 4 +- ...mmopathy-of-undetermined-significance.html | 4 +- ...unoglobulin-deposition-diseases-other.html | 4 +- ...al-immunoglobulin-deposition-diseases.html | 4 +- ...eliotropic-intestinal-t-cell-lymphoma.html | 4 +- ...omorphic-ptld-b--and-t--nk-cell-types.html | 4 +- .../montefiore-einstein-cancer-center.html | 4 +- .../moores-comprehensive-cancer-center.html | 4 +- concepts/mrisktool.html | 4 +- concepts/mt-sdna.html | 4 +- concepts/mu-heavy-chain-disease.html | 4 +- ...cinous-adenocarcinoma-of-the-appendix.html | 4 +- ...denocarcinoma-of-the-colon-and-rectum.html | 4 +- ...us-adenocarcinoma-of-the-vulva-vagina.html | 4 +- .../mucinous-borderline-ovarian-tumor.html | 4 +- concepts/mucinous-carcinoma.html | 4 +- concepts/mucinous-cystic-neoplasm.html | 4 +- concepts/mucinous-ovarian-cancer.html | 4 +- concepts/mucinous-stomach-adenocarcinoma.html | 4 +- .../mucoepidermoid-carcinoma-of-the-lung.html | 4 +- concepts/mucoepidermoid-carcinoma.html | 4 +- .../mucosal-melanoma-of-the-esophagus.html | 4 +- concepts/mucosal-melanoma-of-the-urethra.html | 4 +- .../mucosal-melanoma-of-the-vulva-vagina.html | 4 +- concepts/multiple-myeloma.html | 4 +- concepts/multiplemyeloma-subreddit.html | 4 +- concepts/music-therapy.html | 4 +- concepts/mycanceriq.html | 4 +- concepts/mychart.html | 4 +- concepts/mycosis-fungoides.html | 4 +- ...splastic-myeloproliferative-neoplasms.html | 4 +- concepts/myelodysplastic-syndromes.html | 4 +- concepts/myeloid-atypical.html | 4 +- concepts/myeloid-benign.html | 4 +- ...eukemia-associated-with-down-syndrome.html | 4 +- ...fra-pdgfrb-or-fgfr1-or-with-pcm1-jak2.html | 4 +- ...id-neoplasms-with-fgfr1-rearrangement.html | 4 +- ...oid-lymphoid-neoplasms-with-pcm1-jak2.html | 4 +- ...d-neoplasms-with-pdgfra-rearrangement.html | 4 +- ...d-neoplasms-with-pdgfrb-rearrangement.html | 4 +- concepts/myeloid-neoplasm.html | 4 +- ...oplasms-with-germ-line-predisposition.html | 4 +- ...oliferations-related-to-down-syndrome.html | 4 +- concepts/myeloid-sarcoma.html | 4 +- concepts/myeloid.html | 4 +- ...roliferative-neoplasms-unclassifiable.html | 4 +- concepts/myeloproliferative-neoplasms.html | 4 +- concepts/myoepithelial-carcinoma.html | 4 +- concepts/myofibroma.html | 4 +- concepts/myofibromatosis.html | 4 +- concepts/myopericytoma.html | 4 +- concepts/myxofibrosarcoma.html | 4 +- concepts/myxoid-chondrosarcoma.html | 4 +- concepts/myxoid-round-cell-liposarcoma.html | 4 +- concepts/myxoma.html | 4 +- concepts/myxopapillary-ependymoma.html | 4 +- concepts/naaccr.html | 4 +- concepts/nacr-ng.html | 4 +- concepts/nasopharyngeal-carcinoma.html | 4 +- .../national-breast-cancer-foundation.html | 4 +- concepts/national-cancer-center-japan.html | 4 +- concepts/national-cancer-center-korea.html | 4 +- concepts/national-cancer-grid-india.html | 4 +- .../national-cancer-institute-brazil.html | 4 +- ...national-cancer-research-institute-uk.html | 4 +- ...ional-prostate-cancer-awareness-month.html | 4 +- ...-cell-lymphoblastic-leukemia-lymphoma.html | 4 +- ...atural-strategies-for-cancer-patients.html | 4 +- concepts/nature-reviews-cancer.html | 4 +- concepts/navigational-bronchoscopy.html | 4 +- concepts/ncbi-gene.html | 4 +- concepts/ncbi.html | 4 +- concepts/ncc.html | 4 +- concepts/nccn.html | 4 +- concepts/nccp-af.html | 4 +- concepts/nccp-al.html | 4 +- concepts/nccr.html | 4 +- concepts/nccs.html | 4 +- concepts/ncdb.html | 4 +- concepts/nci.html | 4 +- concepts/ncr-ar.html | 4 +- concepts/ncra.html | 4 +- concepts/ncrsa-za.html | 4 +- concepts/nedaplatin.html | 4 +- concepts/nephrectomy.html | 4 +- concepts/nerve-sheath-tumor.html | 4 +- concepts/netherlands-ncc.html | 4 +- concepts/neuroblastoma.html | 4 +- concepts/neuroendocrine-carcinoma-nos.html | 4 +- concepts/neuroendocrine-tumor-nos.html | 4 +- concepts/neurofibroma.html | 4 +- concepts/newyork-presbyterian-hospital.html | 4 +- concepts/nfr.html | 4 +- concepts/nhgri.html | 4 +- concepts/nicr.html | 4 +- concepts/nih.html | 4 +- concepts/nimustine.html | 4 +- concepts/nitrosoureas.html | 4 +- concepts/nivolumab.html | 4 +- concepts/nlm.html | 4 +- concepts/nncr-na.html | 4 +- concepts/nodal-marginal-zone-lymphoma.html | 4 +- ...al-t-cell-lymphoma-with-tfh-phenotype.html | 4 +- ...mphocyte-predominant-hodgkin-lymphoma.html | 4 +- ...-sclerosis-classical-hodgkin-lymphoma.html | 4 +- concepts/non-hodgkin-lymphoma.html | 4 +- .../non-seminomatous-germ-cell-tumor.html | 4 +- concepts/non-small-cell-lung-cancer.html | 4 +- concepts/norwegian-cancer-society.html | 4 +- concepts/novartis.html | 4 +- concepts/novo-nordisk.html | 4 +- concepts/nscr-ng.html | 4 +- concepts/nswcr-au.html | 4 +- concepts/ntcr-au.html | 4 +- concepts/nut-carcinoma-of-the-lung.html | 4 +- ...idline-carcinoma-of-the-head-and-neck.html | 4 +- concepts/nutrition.html | 4 +- concepts/ocular-melanoma.html | 4 +- concepts/odontogenic-carcinoma.html | 4 +- concepts/olfactory-neuroblastoma.html | 4 +- concepts/oligoastrocytoma.html | 4 +- concepts/oligodendroglioma.html | 4 +- concepts/onclive.html | 4 +- .../oncocytic-adenoma-of-the-thyroid.html | 4 +- concepts/oncokb.html | 4 +- concepts/oncotree.html | 4 +- concepts/one-plus-one-cars.html | 4 +- .../oneal-comprehensive-cancer-center.html | 4 +- concepts/oophorectomy.html | 4 +- concepts/oral-administration.html | 4 +- concepts/oral-cancer-foundation.html | 4 +- concepts/oral-cancer.html | 4 +- .../oral-cavity-squamous-cell-carcinoma.html | 4 +- concepts/orchiectomy.html | 4 +- concepts/oropharyngeal-cancer.html | 4 +- .../oropharynx-squamous-cell-carcinoma.html | 4 +- concepts/ossifying-fibromyxoid-tumor.html | 4 +- concepts/osteoblastic-osteosarcoma.html | 4 +- concepts/osteoclastic-giant-cell-tumor.html | 4 +- concepts/osteosarcoma.html | 4 +- concepts/other-uterine-tumor.html | 4 +- concepts/other.html | 4 +- concepts/our-world-in-data-cancer.html | 4 +- concepts/ovarian-cancer-other.html | 4 +- concepts/ovarian-cancer-subreddit.html | 4 +- concepts/ovarian-cancer.html | 4 +- ...coma-malignant-mixed-mesodermal-tumor.html | 4 +- concepts/ovarian-choriocarcinoma-nos.html | 4 +- concepts/ovarian-epithelial-tumor.html | 4 +- concepts/ovarian-germ-cell-tumor.html | 4 +- concepts/ovarian-seromucinous-adenoma.html | 4 +- ...ovarian-seromucinous-borderline-tumor.html | 4 +- concepts/ovarian-seromucinous-carcinoma.html | 4 +- concepts/oxaliplatin.html | 4 +- concepts/paclitaxel.html | 4 +- concepts/pact.html | 4 +- concepts/paget-disease-of-the-nipple.html | 4 +- concepts/paleo-diet.html | 4 +- concepts/panama-ncc.html | 4 +- concepts/pancreas.html | 4 +- concepts/pancreatectomy.html | 4 +- concepts/pancreatic-adenocarcinoma.html | 4 +- concepts/pancreatic-neuroendocrine-tumor.html | 4 +- .../pancreatobiliary-ampullary-carcinoma.html | 4 +- concepts/pancreatoblastoma.html | 4 +- concepts/papillary-glioneuronal-tumor.html | 4 +- concepts/papillary-meningioma.html | 4 +- concepts/papillary-renal-cell-carcinoma.html | 4 +- .../papillary-stomach-adenocarcinoma.html | 4 +- concepts/papillary-thyroid-cancer.html | 4 +- .../papillary-tumor-of-the-pineal-region.html | 4 +- concepts/paraganglioma.html | 4 +- concepts/parathyroid-cancer.html | 4 +- concepts/parathyroid-carcinoma.html | 4 +- concepts/parosteal-osteosarcoma.html | 4 +- concepts/partial-hydatidiform-mole.html | 4 +- .../pediatric-type-follicular-lymphoma.html | 4 +- concepts/pembrolizumab.html | 4 +- concepts/penile-squamous-cell-carcinoma.html | 4 +- concepts/penis.html | 4 +- concepts/perihilar-cholangiocarcinoma.html | 4 +- concepts/periosteal-osteosarcoma.html | 4 +- concepts/peripheral-nervous-system.html | 4 +- concepts/peripheral-t-cell-lymphoma-nos.html | 4 +- concepts/peritoneal-mesothelioma.html | 4 +- concepts/peritoneal-serous-carcinoma.html | 4 +- concepts/peritoneum.html | 4 +- .../perivascular-epithelioid-cell-tumor.html | 4 +- concepts/pfizer.html | 4 +- concepts/phac.html | 4 +- concepts/pheochromocytoma.html | 4 +- concepts/phyllodes-tumor-of-the-breast.html | 4 +- concepts/pilocytic-astrocytoma.html | 4 +- concepts/pilomyxoid-astrocytoma.html | 4 +- ...tumor-of-intermediate-differentiation.html | 4 +- concepts/pineal-tumor.html | 4 +- concepts/pineoblastoma.html | 4 +- concepts/pineocytoma.html | 4 +- concepts/pipobroman.html | 4 +- concepts/pirarubicin.html | 4 +- concepts/pituicytoma.html | 4 +- concepts/pituitary-adenoma.html | 4 +- concepts/pituitary-carcinoma.html | 4 +- .../placental-site-trophoblastic-tumor.html | 4 +- concepts/plasma-cell-myeloma.html | 4 +- concepts/plasmablastic-lymphoma.html | 4 +- concepts/plasmacytic-hyperplasia-ptld.html | 4 +- ...id-signet-ring-cell-bladder-carcinoma.html | 4 +- .../pleomorphic-carcinoma-of-the-lung.html | 4 +- concepts/pleomorphic-liposarcoma.html | 4 +- concepts/pleomorphic-rhabdomyosarcoma.html | 4 +- concepts/pleomorphic-xanthoastrocytoma.html | 4 +- concepts/pleura.html | 4 +- .../pleural-mesothelioma-biphasic-type.html | 4 +- ...pleural-mesothelioma-epithelioid-type.html | 4 +- ...pleural-mesothelioma-sarcomatoid-type.html | 4 +- concepts/pleural-mesothelioma.html | 4 +- concepts/pleuropulmonary-blastoma.html | 4 +- .../polycythaemia-vera-myelofibrosis.html | 4 +- concepts/polycythemia-vera.html | 4 +- concepts/polyembryoma-vulva-vagina.html | 4 +- concepts/polyembryoma.html | 4 +- concepts/polymorphic-ptld.html | 4 +- .../poorly-differentiated-carcinoma-nos.html | 4 +- ...fferentiated-carcinoma-of-the-stomach.html | 4 +- ...ifferentiated-carcinoma-of-the-uterus.html | 4 +- ...erentiated-non-small-cell-lung-cancer.html | 4 +- .../poorly-differentiated-thyroid-cancer.html | 4 +- ...orly-differentiated-vaginal-carcinoma.html | 4 +- .../porocarcinoma-spiroadenocarcinoma.html | 4 +- concepts/poroma-acrospiroma.html | 4 +- concepts/porphyria-cutania-tarda.html | 4 +- ...nsplant-lymphoproliferative-disorders.html | 4 +- concepts/prevent-cancer-foundation.html | 4 +- concepts/primary-brain-tumor.html | 4 +- concepts/primary-cns-melanocytic-tumors.html | 4 +- concepts/primary-cns-melanoma.html | 4 +- ...us-acral-cd8-positive-t-cell-lymphoma.html | 4 +- ...aneous-anaplastic-large-cell-lymphoma.html | 4 +- ...-t-cell-lymphoproliferative-disorders.html | 4 +- ...m-t-cell-lymphoproliferative-disorder.html | 4 +- ...dermotropic-cytotoxic-t-cell-lymphoma.html | 4 +- .../primary-cutaneous-dlbcl-leg-type.html | 4 +- ...ry-cutaneous-follicle-center-lymphoma.html | 4 +- ...cutaneous-gamma-delta-t-cell-lymphoma.html | 4 +- ...y-dlbcl-of-the-central-nervous-system.html | 4 +- concepts/primary-effusion-lymphoma.html | 4 +- ...iastinal-thymic-large-b-cell-lymphoma.html | 4 +- ...myelofibrosis-prefibrotic-early-stage.html | 4 +- concepts/primary-myelofibrosis.html | 4 +- ...ary-myelofibrosisovert-fibrotic-stage.html | 4 +- concepts/primary-neuroepithelial-tumor.html | 4 +- concepts/primitive-neuroectodermal-tumor.html | 4 +- concepts/princess-margaret-cancer-centre.html | 4 +- .../proliferating-pilar-cystic-tumor.html | 4 +- concepts/prostate-adenocarcinoma.html | 4 +- concepts/prostate-cancer-subreddit.html | 4 +- .../prostate-neuroendocrine-carcinoma.html | 4 +- concepts/prostate-small-cell-carcinoma.html | 4 +- .../prostate-squamous-cell-carcinoma.html | 4 +- concepts/prostate.html | 4 +- concepts/prostatectomy.html | 4 +- .../proximal-type-epithelioid-sarcoma.html | 4 +- .../pseudomyogenic-hemangioendothelioma.html | 4 +- concepts/pubmed-central.html | 4 +- concepts/pubmed.html | 4 +- concepts/pulmonary-lymphangiomyomatosis.html | 4 +- concepts/pulmonary-metastasectomy.html | 4 +- .../purdue-institute-for-cancer-research.html | 4 +- concepts/pure-erythroid-leukemia.html | 4 +- concepts/qatar-ncc.html | 4 +- concepts/qcancer.html | 4 +- concepts/qcr-au.html | 4 +- concepts/radiation-associated-sarcoma.html | 4 +- ...ion-surviving-cancer-against-all-odds.html | 4 +- concepts/radioembolization.html | 4 +- concepts/radiofrequency-ablation.html | 4 +- concepts/ranimustine.html | 4 +- concepts/rdca-ci.html | 4 +- concepts/rdca-dz.html | 4 +- concepts/rdcb-mo.html | 4 +- concepts/rdcdb-cg.html | 4 +- concepts/rdcg-gu.html | 4 +- concepts/rdclr.html | 4 +- concepts/rdcm-ma.html | 4 +- concepts/rectal-adenocarcinoma.html | 4 +- .../refractory-cytopenia-of-childhood.html | 4 +- concepts/regeneron.html | 4 +- concepts/regorafenib.html | 4 +- concepts/renal-angiomyolipoma.html | 4 +- concepts/renal-cell-carcinoma.html | 4 +- ...l-carcinoma-with-sarcomatoid-features.html | 4 +- concepts/renal-clear-cell-carcinoma.html | 4 +- concepts/renal-medullary-carcinoma.html | 4 +- ...cinous-tubular-spindle-cell-carcinoma.html | 4 +- concepts/renal-neuroendocrine-tumor.html | 4 +- concepts/renal-non-clear-cell-carcinoma.html | 4 +- concepts/renal-oncocytoma.html | 4 +- concepts/renal-small-cell-carcinoma.html | 4 +- concepts/retinoblastoma.html | 4 +- concepts/rhabdoid-cancer.html | 4 +- concepts/rhabdoid-meningioma.html | 4 +- concepts/rhabdomyosarcoma.html | 4 +- .../rivkin-center-risk-assessment-tool.html | 4 +- ...t-h-lurie-comprehensive-cancer-center.html | 4 +- concepts/robotic-assisted-bronchoscopy.html | 4 +- concepts/roche.html | 4 +- concepts/romidepsin.html | 4 +- concepts/rosai-dorfman-disease.html | 4 +- ...euronal-tumor-of-the-fourth-ventricle.html | 4 +- ...well-park-comprehensive-cancer-center.html | 4 +- concepts/round-cell-sarcoma-nos.html | 4 +- concepts/rpcc.html | 4 +- concepts/rpt-ar.html | 4 +- concepts/rush-university-medical-center.html | 4 +- ...utgers-cancer-institute-of-new-jersey.html | 4 +- concepts/sacr-au.html | 4 +- concepts/saecpcr-za.html | 4 +- concepts/salivary-adenocarcinoma.html | 4 +- concepts/salivary-carcinoma-other.html | 4 +- concepts/salivary-carcinoma.html | 4 +- concepts/salivary-duct-carcinoma.html | 4 +- concepts/salivary-gland-oncocytoma.html | 4 +- ...salivary-gland-type-tumor-of-the-lung.html | 4 +- concepts/salk-institute-cancer-center.html | 4 +- ...am-prebys-medical-discovery-institute.html | 4 +- concepts/sanofi.html | 4 +- concepts/sarcoma-nos.html | 4 +- .../sarcomatoid-carcinoma-of-the-lung.html | 4 +- ...toid-carcinoma-of-the-urinary-bladder.html | 4 +- .../sarcomatoid-renal-cell-carcinoma.html | 4 +- concepts/satraplatin.html | 4 +- concepts/sauna.html | 4 +- concepts/sbrt.html | 4 +- concepts/schwannoma.html | 4 +- .../sclerosing-epithelioid-fibrosarcoma.html | 4 +- concepts/scr-dz.html | 4 +- concepts/seattle-childrens-hospital.html | 4 +- concepts/sebaceous-carcinoma.html | 4 +- concepts/secondary-osteosarcoma.html | 4 +- concepts/seer.html | 4 +- concepts/sellar-tumor.html | 4 +- concepts/seminoma.html | 4 +- concepts/semustine.html | 4 +- concepts/serene-hospice-care.html | 4 +- ...rderline-ovarian-tumor-micropapillary.html | 4 +- concepts/serous-borderline-ovarian-tumor.html | 4 +- .../serous-cystadenoma-of-the-pancreas.html | 4 +- concepts/serous-ovarian-cancer.html | 4 +- concepts/sertoli-leydig-cell-tumor.html | 4 +- concepts/sex-cord-stromal-tumor-testis.html | 4 +- concepts/sex-cord-stromal-tumor.html | 4 +- concepts/sezary-syndrome.html | 4 +- concepts/sialoblastoma.html | 4 +- ...center-at-thomas-jefferson-university.html | 4 +- ...ey-kimmel-comprehensive-cancer-center.html | 4 +- concepts/sigmoidoscopy.html | 4 +- ...denocarcinoma-of-the-colon-and-rectum.html | 4 +- ...et-ring-cell-carcinoma-of-the-stomach.html | 4 +- ...signet-ring-cell-type-of-the-appendix.html | 4 +- concepts/signet-ring-mucinous-carcinoma.html | 4 +- concepts/sinonasal-adenocarcinoma.html | 4 +- .../sinonasal-squamous-cell-carcinoma.html | 4 +- .../sinonasal-undifferentiated-carcinoma.html | 4 +- concepts/sinopharm.html | 4 +- concepts/sitc.html | 4 +- concepts/skin-adnexal-carcinoma.html | 4 +- concepts/skin-cancer-subreddit.html | 4 +- concepts/skin.html | 4 +- concepts/sleep.html | 4 +- concepts/small-bowel-cancer.html | 4 +- ...l-differentiated-neuroendocrine-tumor.html | 4 +- concepts/small-cell-bladder-cancer.html | 4 +- .../small-cell-carcinoma-of-the-cervix.html | 4 +- .../small-cell-carcinoma-of-the-ovary.html | 4 +- .../small-cell-carcinoma-of-the-stomach.html | 4 +- ...all-cell-carcinoma-of-unknown-primary.html | 4 +- .../small-cell-gallbladder-carcinoma.html | 4 +- concepts/small-cell-glioblastoma.html | 4 +- concepts/small-cell-lung-cancer.html | 4 +- concepts/small-cell-osteosarcoma.html | 4 +- concepts/small-intestinal-carcinoma.html | 4 +- concepts/smoking-cessation.html | 4 +- .../smoldering-systemic-mastocytosis.html | 4 +- concepts/smooth-muscle-neoplasm-nos.html | 4 +- concepts/sncr-sy.html | 4 +- .../soft-tissue-myoepithelial-carcinoma.html | 4 +- concepts/soft-tissue.html | 4 +- ...lid-papillary-carcinoma-of-the-breast.html | 4 +- ...udopapillary-neoplasm-of-the-pancreas.html | 4 +- ...tary-fibrous-tumor-hemangiopericytoma.html | 4 +- ...s-tumor-of-the-central-nervous-system.html | 4 +- concepts/solitary-plasmacytoma-of-bone.html | 4 +- concepts/sorafenib.html | 4 +- .../spindle-cell-carcinoma-of-the-lung.html | 4 +- ...ell-oncocytoma-of-the-adenohypophysis.html | 4 +- concepts/spindle-cell-rhabdomyosarcoma.html | 4 +- ...ndle-cell-sclerosing-rhabdomyosarcoma.html | 4 +- concepts/spiroma-spiradenoma.html | 4 +- concepts/spitzoid-melanoma.html | 4 +- concepts/splenectomy.html | 4 +- ...cell-lymphoma-leukemia-unclassifiable.html | 4 +- ...iffuse-red-pulp-small-b-cell-lymphoma.html | 4 +- concepts/splenic-marginal-zone-lymphoma.html | 4 +- ...us-cell-carcinoma-of-the-vulva-vagina.html | 4 +- concepts/squamous-cell-carcinoma.html | 4 +- .../st-jude-childrens-research-hospital.html | 4 +- concepts/stanford-cancer-institute-sci.html | 4 +- concepts/steam-bath.html | 4 +- concepts/stephenson-cancer-center.html | 4 +- concepts/steroid-cell-tumor-nos.html | 4 +- concepts/stomach-adenocarcinoma.html | 4 +- concepts/stomach-cancer.html | 4 +- concepts/streptozotocin.html | 4 +- concepts/stress-management.html | 4 +- concepts/subcutaneous-administration.html | 4 +- ...ous-panniculitis-like-t-cell-lymphoma.html | 4 +- concepts/subependymoma.html | 4 +- concepts/susan-g-komen.html | 4 +- concepts/sweat-gland-adenocarcinoma.html | 4 +- ...-carcinoma-apocrine-eccrine-carcinoma.html | 4 +- concepts/sweden-nch.html | 4 +- concepts/swedish-cancer-institute.html | 4 +- .../swedish-national-cancer-register.html | 4 +- concepts/swiss-cancer-league.html | 4 +- concepts/swog.html | 4 +- ...sylvester-comprehensive-cancer-center.html | 4 +- concepts/synovial-sarcoma.html | 4 +- ...positive-t-cell-lymphoma-of-childhood.html | 4 +- ...-an-associated-hematological-neoplasm.html | 4 +- concepts/systemic-mastocytosis.html | 4 +- ...histiocyte-rich-large-b-cell-lymphoma.html | 4 +- ...l-large-granular-lymphocytic-leukemia.html | 4 +- concepts/t-cell-prolymphocytic-leukemia.html | 4 +- .../t-lymphoblastic-leukemia-lymphoma.html | 4 +- concepts/tai-chi.html | 4 +- concepts/takeda.html | 4 +- concepts/tamoxifen.html | 4 +- concepts/taxotere.html | 4 +- concepts/tcga.html | 4 +- concepts/tcr-au.html | 4 +- concepts/telangiectatic-osteosarcoma.html | 4 +- concepts/temozolomide.html | 4 +- concepts/teniposide.html | 4 +- ...ynovial-giant-cell-tumor-diffuse-type.html | 4 +- concepts/teratoma.html | 4 +- concepts/tesetaxel.html | 4 +- concepts/testicular-lymphoma.html | 4 +- concepts/testicular-mesothelioma.html | 4 +- concepts/testis.html | 4 +- concepts/teva.html | 4 +- ...barbara-ann-karmanos-cancer-institute.html | 4 +- concepts/the-biology-of-cancer.html | 4 +- ...ght-hour-a-memoir-of-living-and-dying.html | 4 +- concepts/the-c-word.html | 4 +- concepts/the-cancer-journals.html | 4 +- concepts/the-death-of-cancer-book.html | 4 +- concepts/the-dog-cancer-survival-guide.html | 4 +- concepts/the-eden-prescription-book.html | 4 +- ...ects-of-herbs-and-fruits-on-leukaemia.html | 4 +- concepts/the-hope-foundation.html | 4 +- concepts/the-hyve.html | 4 +- .../the-immortal-life-of-henrietta-lacks.html | 4 +- .../the-jackson-laboratory-cancer-center.html | 4 +- concepts/the-lancet-oncology.html | 4 +- .../the-metabolic-approach-to-cancer.html | 4 +- ...niversity-comprehensive-cancer-center.html | 4 +- concepts/the-skin-cancer-foundation.html | 4 +- concepts/the-truth-about-cancer.html | 4 +- concepts/the-truth-in-small-doses-book.html | 4 +- ...f-chicago-comprehensive-cancer-center.html | 4 +- ...he-university-of-kansas-cancer-center.html | 4 +- .../the-wistar-institute-cancer-center.html | 4 +- ...herapy-related-acute-myeloid-leukemia.html | 4 +- ...rapy-related-myelodysplastic-syndrome.html | 4 +- .../therapy-related-myeloid-neoplasms.html | 4 +- concepts/thiotepa.html | 4 +- concepts/thymic-carcinoma.html | 4 +- concepts/thymic-epithelial-tumor.html | 4 +- concepts/thymic-neuroendocrine-tumor.html | 4 +- concepts/thymoma.html | 4 +- concepts/thymus.html | 4 +- concepts/thyroid-cancer-subreddit.html | 4 +- concepts/thyroid.html | 4 +- concepts/thyroidectomy.html | 4 +- concepts/tioguanine.html | 4 +- concepts/tisch-cancer-institute.html | 4 +- concepts/tlcr-dz.html | 4 +- concepts/tocr-dz.html | 4 +- concepts/topical-medication.html | 4 +- concepts/topotecan.html | 4 +- concepts/toxin-avoidance.html | 4 +- concepts/transarterial-chemoembolization.html | 4 +- concepts/transarterial-embolization.html | 4 +- concepts/transient-abnormal-myelopoiesis.html | 4 +- ...ation-associated-renal-cell-carcinoma.html | 4 +- concepts/treosulfan.html | 4 +- concepts/tretinoin.html | 4 +- concepts/triaziquone.html | 4 +- concepts/triethylenemelamine.html | 4 +- concepts/trofosfamide.html | 4 +- concepts/tubular-adenoma-of-the-colon.html | 4 +- concepts/tubular-stomach-adenocarcinoma.html | 4 +- .../uc-davis-comprehensive-cancer-center.html | 4 +- concepts/uccc.html | 4 +- ...er-family-comprehensive-cancer-center.html | 4 +- concepts/ue-lifesciences.html | 4 +- concepts/uicc.html | 4 +- concepts/umls.html | 4 +- concepts/un.html | 4 +- ...ineberger-comprehensive-cancer-center.html | 4 +- .../unclassified-renal-cell-carcinoma.html | 4 +- ...ferentiated-carcinoma-of-the-pancreas.html | 4 +- ...tiated-embryonal-sarcoma-of-the-liver.html | 4 +- .../undifferentiated-malignant-neoplasm.html | 4 +- ...ytoma-high-grade-spindle-cell-sarcoma.html | 4 +- ...differentiated-stomach-adenocarcinoma.html | 4 +- .../undifferentiated-uterine-sarcoma.html | 4 +- concepts/unicancer.html | 4 +- .../university-of-colorado-cancer-center.html | 4 +- .../university-of-hawaii-cancer-center.html | 4 +- ...reenebaum-comprehensive-cancer-center.html | 4 +- ...rsity-of-michigan-rogel-cancer-center.html | 4 +- ...-cancer-research-and-treatment-center.html | 4 +- .../university-of-virginia-cancer-center.html | 4 +- ...ty-of-wisconsin-carbone-cancer-center.html | 4 +- concepts/upmc-hillman-cancer-center.html | 4 +- .../upper-tract-urothelial-carcinoma.html | 4 +- concepts/urachal-adenocarcinoma.html | 4 +- concepts/urachal-carcinoma.html | 4 +- concepts/uramustine.html | 4 +- concepts/urethral-adenocarcinoma.html | 4 +- concepts/urethral-cancer.html | 4 +- .../urethral-squamous-cell-carcinoma.html | 4 +- concepts/urethral-urothelial-carcinoma.html | 4 +- concepts/urothelial-papilloma.html | 4 +- ...sc-norris-comprehensive-cancer-center.html | 4 +- concepts/uscs.html | 4 +- concepts/uterine-adenosarcoma.html | 4 +- concepts/uterine-adenosquamous-carcinoma.html | 4 +- ...erine-malignant-mixed-mullerian-tumor.html | 4 +- concepts/uterine-clear-cell-carcinoma.html | 4 +- .../uterine-dedifferentiated-carcinoma.html | 4 +- concepts/uterine-endometrioid-carcinoma.html | 4 +- .../uterine-epithelioid-leiomyosarcoma.html | 4 +- concepts/uterine-leiomyoma.html | 4 +- concepts/uterine-leiomyosarcoma.html | 4 +- concepts/uterine-mesonephric-carcinoma.html | 4 +- .../uterine-mixed-endometrial-carcinoma.html | 4 +- concepts/uterine-mucinous-carcinoma.html | 4 +- concepts/uterine-myxoid-leiomyosarcoma.html | 4 +- .../uterine-neuroendocrine-carcinoma.html | 4 +- ...e-perivascular-epithelioid-cell-tumor.html | 4 +- concepts/uterine-sarcoma-mesenchymal.html | 4 +- concepts/uterine-sarcoma-other.html | 4 +- ...ma-uterine-papillary-serous-carcinoma.html | 4 +- ...umor-of-uncertain-malignant-potential.html | 4 +- concepts/uterine-smooth-muscle-tumor.html | 4 +- .../uterine-undifferentiated-carcinoma.html | 4 +- concepts/uterus.html | 4 +- concepts/uveal-melanoma.html | 4 +- concepts/vacf.html | 4 +- concepts/vaginal-adenocarcinoma.html | 4 +- concepts/valrubicin.html | 4 +- concepts/vanderbilt-ingram-cancer-center.html | 4 +- concepts/vats.html | 4 +- concepts/vcr-au.html | 4 +- concepts/veganism.html | 4 +- concepts/vegetarianism.html | 4 +- concepts/vemurafenib.html | 4 +- ...rucous-penile-squamous-cell-carcinoma.html | 4 +- concepts/viatris.html | 4 +- ...landular-adenocarcinoma-of-the-cervix.html | 4 +- concepts/villoglandular-carcinoma.html | 4 +- concepts/vinblastine.html | 4 +- concepts/vincristine.html | 4 +- concepts/vindesine.html | 4 +- concepts/vinorelbine.html | 4 +- concepts/vismodegib.html | 4 +- concepts/vitamin-a.html | 4 +- concepts/vitamin-b.html | 4 +- concepts/vitamin-c.html | 4 +- concepts/vitamin-d.html | 4 +- concepts/vorinostat.html | 4 +- concepts/vulva-vagina.html | 4 +- concepts/wacanreg.html | 4 +- ...t-baptist-comprehensive-cancer-center.html | 4 +- concepts/waldenstrom-macroglobulinemia.html | 4 +- .../warty-penile-squamous-cell-carcinoma.html | 4 +- concepts/wcrf-uk.html | 4 +- concepts/wcrf.html | 4 +- concepts/weight-loss.html | 4 +- concepts/well-differentiated-liposarcoma.html | 4 +- ...-neuroendocrine-tumor-of-the-appendix.html | 4 +- ...ed-neuroendocrine-tumor-of-the-rectum.html | 4 +- ...-neuroendocrine-tumors-of-the-stomach.html | 4 +- .../well-differentiated-thyroid-cancer.html | 4 +- concepts/when-breath-becomes-air.html | 4 +- concepts/who.html | 4 +- concepts/wilms-tumor.html | 4 +- concepts/wim-hof-method.html | 4 +- ...c-journey-towards-a-natural-cure-book.html | 4 +- concepts/winship-cancer-institute.html | 4 +- concepts/wkof.html | 4 +- concepts/world-cancer-day.html | 4 +- concepts/world-lymphoma-awareness-day.html | 4 +- concepts/yale-cancer-center.html | 4 +- .../yale-introduction-to-breast-cancer.html | 4 +- concepts/yoga.html | 4 +- concepts/yolk-sac-tumor-cns-brain.html | 4 +- concepts/yolk-sac-tumor-testis.html | 4 +- concepts/yolk-sac-tumor-vulva-vagina.html | 4 +- concepts/yolk-sac-tumor.html | 4 +- concepts/your-disease-risk.html | 4 +- concepts/zmncr-zm.html | 4 +- concepts/zncr-zw.html | 4 +- concepts/zorubicin.html | 4 +- csv.html | 4 +- download.html | 4 +- index.html | 4 +- list.html | 4 +- node_modules/.package-lock.json | 6 +- .../scroll-cli/external/codeMirror.css | 521 + .../scroll-cli/external/copyPaster.js | 71 - .../scroll-cli/external/scrollLibs.js | 19159 ++++++++++++++++ node_modules/scroll-cli/package.json | 2 +- node_modules/scroll-cli/parsers/forms.parsers | 18 +- node_modules/scroll-cli/readme.scroll | 2 +- node_modules/scroll-cli/scroll.js | 25 + package-lock.json | 8 +- package.json | 2 +- pages/about.html | 4 +- pages/acknowledgements.html | 4 +- pages/explore.html | 4 +- scrollLibs.js | 19159 ++++++++++++++++ search.html | 4 +- 1489 files changed, 42887 insertions(+), 3037 deletions(-) create mode 100644 codeMirror.css create mode 100644 node_modules/scroll-cli/external/codeMirror.css delete mode 100644 node_modules/scroll-cli/external/copyPaster.js create mode 100644 node_modules/scroll-cli/external/scrollLibs.js create mode 100644 scrollLibs.js diff --git a/404.html b/404.html index 27370d5709..cb0210a99b 100644 --- a/404.html +++ b/404.html @@ -3,12 +3,12 @@ Page not found - + - + @@ -37,7 +37,7 @@

If you think there's a bug on our site, please email help@cancerdb.com or open an issue to let us know. Thank you!

-Built with Scroll v126.0.1 +Built with Scroll v126.1.0
\ No newline at end of file diff --git a/add.html b/add.html index 0d8d5ca3ef..f5033970d5 100644 --- a/add.html +++ b/add.html @@ -3,12 +3,12 @@ CancerDB - Add Concept - + - + @@ -142,6 +142,9 @@
+ + +
+
+ +
View source
diff --git a/blog/cancer-heatmaps.html b/blog/cancer-heatmaps.html index 604c4fe365..bf1ffdd2d7 100644 --- a/blog/cancer-heatmaps.html +++ b/blog/cancer-heatmaps.html @@ -3,12 +3,12 @@ Cancer in the United States - + - + diff --git a/blog/cat-food.html b/blog/cat-food.html index 4e6db75757..ea481f31ff 100644 --- a/blog/cat-food.html +++ b/blog/cat-food.html @@ -3,12 +3,12 @@ Cat food - + - + diff --git a/blog/commercials.html b/blog/commercials.html index 65aa14d9d8..a9a172dd98 100644 --- a/blog/commercials.html +++ b/blog/commercials.html @@ -3,12 +3,12 @@ CancerDB Commercials - + - + diff --git a/blog/feed.xml b/blog/feed.xml index 510f93fb08..b3503ef55b 100644 --- a/blog/feed.xml +++ b/blog/feed.xml @@ -4,7 +4,7 @@ Feed https://cancerdb.com/ CancerDB: Datasets about Cancer - Tue, 03 Sep 2024 17:22:38 +0000 + Tue, 03 Sep 2024 21:10:14 +0000 en-us diff --git a/blog/funQuiz.html b/blog/funQuiz.html index 24ab28f71e..98c53601e8 100644 --- a/blog/funQuiz.html +++ b/blog/funQuiz.html @@ -3,12 +3,12 @@ Fun quiz - + - + diff --git a/blog/fund.html b/blog/fund.html index 3308d92730..a832f6d533 100644 --- a/blog/fund.html +++ b/blog/fund.html @@ -3,12 +3,12 @@ On Our Slow Pace - + - + diff --git a/blog/hawaii-cancer-moonshot.html b/blog/hawaii-cancer-moonshot.html index 552268116f..cc5fde5a37 100644 --- a/blog/hawaii-cancer-moonshot.html +++ b/blog/hawaii-cancer-moonshot.html @@ -3,12 +3,12 @@ The Hawai'i #CancerMoonshot - + - + diff --git a/blog/holey-moley.html b/blog/holey-moley.html index ed7f07acea..42aed79d6a 100644 --- a/blog/holey-moley.html +++ b/blog/holey-moley.html @@ -3,12 +3,12 @@ Holey Moley - + - + diff --git a/blog/humor.html b/blog/humor.html index 87d2856ccd..b19f4b07bd 100644 --- a/blog/humor.html +++ b/blog/humor.html @@ -3,12 +3,12 @@ CancerDB Commercials - + - + diff --git a/blog/index.html b/blog/index.html index 91471756c4..710c52c604 100644 --- a/blog/index.html +++ b/blog/index.html @@ -3,12 +3,12 @@ The CancerDB Blog - + - + diff --git a/blog/moonbathing.html b/blog/moonbathing.html index cee9331f37..f8c0f491b6 100644 --- a/blog/moonbathing.html +++ b/blog/moonbathing.html @@ -3,12 +3,12 @@ Moonbathing - + - + diff --git a/blog/natureCalls.html b/blog/natureCalls.html index 885c1c91fe..1cbfcff58b 100644 --- a/blog/natureCalls.html +++ b/blog/natureCalls.html @@ -3,12 +3,12 @@ Nature calls - + - + diff --git a/blog/prostateExams.html b/blog/prostateExams.html index b3ddf8674c..5315cb5d16 100644 --- a/blog/prostateExams.html +++ b/blog/prostateExams.html @@ -3,12 +3,12 @@ Prostate Exams - + - + diff --git a/blog/sayCheese.html b/blog/sayCheese.html index a847a1067a..ce2e319cf0 100644 --- a/blog/sayCheese.html +++ b/blog/sayCheese.html @@ -3,12 +3,12 @@ Say cheese - + - + diff --git a/codeMirror.css b/codeMirror.css new file mode 100644 index 0000000000..7b2475025d --- /dev/null +++ b/codeMirror.css @@ -0,0 +1,521 @@ +/* BASICS */ + +.CodeMirror { + /* Set height, width, borders, and global font properties here */ + font-family: monospace; + height: 300px; + color: black; + direction: ltr; +} + +/* PADDING */ + +.CodeMirror-lines { + padding: 4px 0; /* Vertical padding around content */ +} +.CodeMirror pre { + padding: 0 4px; /* Horizontal padding of content */ +} + +.CodeMirror-scrollbar-filler, +.CodeMirror-gutter-filler { + background-color: white; /* The little square between H and V scrollbars */ +} + +/* GUTTER */ + +.CodeMirror-gutters { + border-right: 1px solid #ddd; + background-color: #f7f7f7; + white-space: nowrap; +} +.CodeMirror-linenumbers { +} +.CodeMirror-linenumber { + padding: 0 3px 0 5px; + min-width: 20px; + text-align: right; + color: #999; + white-space: nowrap; +} + +.CodeMirror-guttermarker { + color: black; +} +.CodeMirror-guttermarker-subtle { + color: #999; +} + +/* CURSOR */ + +.CodeMirror-cursor { + border-left: 1px solid black; + border-right: none; + width: 0; +} +/* Shown when moving in bi-directional text */ +.CodeMirror div.CodeMirror-secondarycursor { + border-left: 1px solid silver; +} +.cm-fat-cursor .CodeMirror-cursor { + width: auto; + border: 0 !important; + background: #7e7; +} +.cm-fat-cursor div.CodeMirror-cursors { + z-index: 1; +} +.cm-fat-cursor-mark { + background-color: rgba(20, 255, 20, 0.5); + -webkit-animation: blink 1.06s steps(1) infinite; + -moz-animation: blink 1.06s steps(1) infinite; + animation: blink 1.06s steps(1) infinite; +} +.cm-animate-fat-cursor { + width: auto; + border: 0; + -webkit-animation: blink 1.06s steps(1) infinite; + -moz-animation: blink 1.06s steps(1) infinite; + animation: blink 1.06s steps(1) infinite; + background-color: #7e7; +} +@-moz-keyframes blink { + 0% { + } + 50% { + background-color: transparent; + } + 100% { + } +} +@-webkit-keyframes blink { + 0% { + } + 50% { + background-color: transparent; + } + 100% { + } +} +@keyframes blink { + 0% { + } + 50% { + background-color: transparent; + } + 100% { + } +} + +/* Can style cursor different in overwrite (non-insert) mode */ +.CodeMirror-overwrite .CodeMirror-cursor { +} + +.cm-tab { + display: inline-block; + text-decoration: inherit; +} + +.CodeMirror-rulers { + position: absolute; + left: 0; + right: 0; + top: -50px; + bottom: -20px; + overflow: hidden; +} +.CodeMirror-ruler { + border-left: 1px solid #ccc; + top: 0; + bottom: 0; + position: absolute; +} + +/* DEFAULT THEME */ + +.cm-s-default .cm-header { + color: blue; +} +.cm-s-default .cm-quote { + color: #090; +} +.cm-negative { + color: #d44; +} +.cm-positive { + color: #292; +} +.cm-header, +.cm-strong { + font-weight: bold; +} +.cm-em { + font-style: italic; +} +.cm-link { + text-decoration: underline; +} +.cm-strikethrough { + text-decoration: line-through; +} + +.cm-s-default .cm-keyword { + color: #708; +} +.cm-s-default .cm-atom { + color: #219; +} +.cm-s-default .cm-number { + color: #164; +} +.cm-s-default .cm-def { + color: #00f; +} +.cm-s-default .cm-variable, +.cm-s-default .cm-punctuation, +.cm-s-default .cm-property, +.cm-s-default .cm-operator { +} +.cm-s-default .cm-variable-2 { + color: #05a; +} +.cm-s-default .cm-variable-3, +.cm-s-default .cm-type { + color: #085; +} +.cm-s-default .cm-comment { + color: #a50; +} +.cm-s-default .cm-string { + color: #a11; +} +.cm-s-default .cm-string-2 { + color: #f50; +} +.cm-s-default .cm-meta { + color: #555; +} +.cm-s-default .cm-qualifier { + color: #555; +} +.cm-s-default .cm-builtin { + color: #30a; +} +.cm-s-default .cm-bracket { + color: #997; +} +.cm-s-default .cm-tag { + color: #170; +} +.cm-s-default .cm-attribute { + color: #00c; +} +.cm-s-default .cm-hr { + color: #999; +} +.cm-s-default .cm-link { + color: #00c; +} + +.cm-s-default .cm-error { + color: #f00; +} +.cm-invalidchar { + color: #f00; +} + +.CodeMirror-composing { + border-bottom: 2px solid; +} + +/* Default styles for common addons */ + +div.CodeMirror span.CodeMirror-matchingbracket { + color: #0b0; +} +div.CodeMirror span.CodeMirror-nonmatchingbracket { + color: #a22; +} +.CodeMirror-matchingtag { + background: rgba(255, 150, 0, 0.3); +} +.CodeMirror-activeline-background { + background: #e8f2ff; +} + +/* STOP */ + +/* The rest of this file contains styles related to the mechanics of + the editor. You probably shouldn't touch them. */ + +.CodeMirror { + position: relative; + overflow: hidden; + background: white; +} + +.CodeMirror-scroll { + overflow: scroll !important; /* Things will break if this is overridden */ + /* 30px is the magic margin used to hide the element's real scrollbars */ + /* See overflow: hidden in .CodeMirror */ + margin-bottom: -30px; + margin-right: -30px; + padding-bottom: 30px; + height: 100%; + outline: none; /* Prevent dragging from highlighting the element */ + position: relative; +} +.CodeMirror-sizer { + position: relative; + border-right: 30px solid transparent; +} + +/* The fake, visible scrollbars. Used to force redraw during scrolling + before actual scrolling happens, thus preventing shaking and + flickering artifacts. */ +.CodeMirror-vscrollbar, +.CodeMirror-hscrollbar, +.CodeMirror-scrollbar-filler, +.CodeMirror-gutter-filler { + position: absolute; + z-index: 6; + display: none; +} +.CodeMirror-vscrollbar { + right: 0; + top: 0; + overflow-x: hidden; + overflow-y: scroll; +} +.CodeMirror-hscrollbar { + bottom: 0; + left: 0; + overflow-y: hidden; + overflow-x: scroll; +} +.CodeMirror-scrollbar-filler { + right: 0; + bottom: 0; +} +.CodeMirror-gutter-filler { + left: 0; + bottom: 0; +} + +.CodeMirror-gutters { + position: absolute; + left: 0; + top: 0; + min-height: 100%; + z-index: 3; +} +.CodeMirror-gutter { + white-space: normal; + height: 100%; + display: inline-block; + vertical-align: top; + margin-bottom: -30px; +} +.CodeMirror-gutter-wrapper { + position: absolute; + z-index: 4; + background: none !important; + border: none !important; +} +.CodeMirror-gutter-background { + position: absolute; + top: 0; + bottom: 0; + z-index: 4; +} +.CodeMirror-gutter-elt { + position: absolute; + cursor: default; + z-index: 4; +} +.CodeMirror-gutter-wrapper ::selection { + background-color: transparent; +} +.CodeMirror-gutter-wrapper ::-moz-selection { + background-color: transparent; +} + +.CodeMirror-lines { + cursor: text; + min-height: 1px; /* prevents collapsing before first draw */ +} +.CodeMirror pre { + /* Reset some styles that the rest of the page might have set */ + -moz-border-radius: 0; + -webkit-border-radius: 0; + border-radius: 0; + border-width: 0; + background: transparent; + font-family: inherit; + font-size: inherit; + margin: 0; + white-space: pre; + word-wrap: normal; + line-height: inherit; + color: inherit; + z-index: 2; + position: relative; + overflow: visible; + -webkit-tap-highlight-color: transparent; + -webkit-font-variant-ligatures: contextual; + font-variant-ligatures: contextual; +} +.CodeMirror-wrap pre { + word-wrap: break-word; + white-space: pre-wrap; + word-break: normal; +} + +.CodeMirror-linebackground { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + z-index: 0; +} + +.CodeMirror-linewidget { + position: relative; + z-index: 2; + padding: 0.1px; /* Force widget margins to stay inside of the container */ +} + +.CodeMirror-widget { +} + +.CodeMirror-rtl pre { + direction: rtl; +} + +.CodeMirror-code { + outline: none; +} + +/* Force content-box sizing for the elements where we expect it */ +.CodeMirror-scroll, +.CodeMirror-sizer, +.CodeMirror-gutter, +.CodeMirror-gutters, +.CodeMirror-linenumber { + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +.CodeMirror-measure { + position: absolute; + width: 100%; + height: 0; + overflow: hidden; + visibility: hidden; +} + +.CodeMirror-cursor { + position: absolute; + pointer-events: none; +} +.CodeMirror-measure pre { + position: static; +} + +div.CodeMirror-cursors { + visibility: hidden; + position: relative; + z-index: 3; +} +div.CodeMirror-dragcursors { + visibility: visible; +} + +.CodeMirror-focused div.CodeMirror-cursors { + visibility: visible; +} + +.CodeMirror-selected { + background: #d9d9d9; +} +.CodeMirror-focused .CodeMirror-selected { + background: #d7d4f0; +} +.CodeMirror-crosshair { + cursor: crosshair; +} +.CodeMirror-line::selection, +.CodeMirror-line > span::selection, +.CodeMirror-line > span > span::selection { + background: #d7d4f0; +} +.CodeMirror-line::-moz-selection, +.CodeMirror-line > span::-moz-selection, +.CodeMirror-line > span > span::-moz-selection { + background: #d7d4f0; +} + +.cm-searching { + background-color: #ffa; + background-color: rgba(255, 255, 0, 0.4); +} + +/* Used to force a border model for a node */ +.cm-force-border { + padding-right: 0.1px; +} + +@media print { + /* Hide the cursor when printing */ + .CodeMirror div.CodeMirror-cursors { + visibility: hidden; + } +} + +/* See issue #2901 */ +.cm-tab-wrap-hack:after { + content: ""; +} + +/* Help users use markselection to safely style text background */ +span.CodeMirror-selectedtext { + background: none; +} + +.CodeMirror-hints { + position: absolute; + z-index: 10; + overflow: hidden; + list-style: none; + + margin: 0; + padding: 2px; + + -webkit-box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2); + box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2); + border-radius: 3px; + border: 1px solid silver; + + background: white; + font-size: 90%; + font-family: monospace; + + max-height: 20em; + overflow-y: auto; +} + +.CodeMirror-hint { + margin: 0; + padding: 0 4px; + border-radius: 2px; + white-space: pre; + color: black; + cursor: pointer; +} + +li.CodeMirror-hint-active { + background: #08f; + color: white; +} diff --git a/concepts/3d-crt.html b/concepts/3d-crt.html index 739e606271..2da0eb177f 100644 --- a/concepts/3d-crt.html +++ b/concepts/3d-crt.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/a-lucky-man-a-memoir.html b/concepts/a-lucky-man-a-memoir.html index 42f5bd702f..231d8d3c48 100644 --- a/concepts/a-lucky-man-a-memoir.html +++ b/concepts/a-lucky-man-a-memoir.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/a-new-war-on-cancer-book.html b/concepts/a-new-war-on-cancer-book.html index 659a94b9b7..6643e246ec 100644 --- a/concepts/a-new-war-on-cancer-book.html +++ b/concepts/a-new-war-on-cancer-book.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/aaccr-et.html b/concepts/aaccr-et.html index e59bbcd7f8..b097512080 100644 --- a/concepts/aaccr-et.html +++ b/concepts/aaccr-et.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/aaci.html b/concepts/aaci.html index c322b5d962..75fdca0616 100644 --- a/concepts/aaci.html +++ b/concepts/aaci.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/aacr.html b/concepts/aacr.html index cdf69511de..56b5ba8710 100644 --- a/concepts/aacr.html +++ b/concepts/aacr.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/aad.html b/concepts/aad.html index 4e82bc6a5d..d0e6848918 100644 --- a/concepts/aad.html +++ b/concepts/aad.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/abbott.html b/concepts/abbott.html index 88bbf7c830..ff6020f642 100644 --- a/concepts/abbott.html +++ b/concepts/abbott.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/abbvie.html b/concepts/abbvie.html index 93bb6c21ae..560776d59b 100644 --- a/concepts/abbvie.html +++ b/concepts/abbvie.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/abramson-cancer-center.html b/concepts/abramson-cancer-center.html index 908cf5720a..3fe403d5be 100644 --- a/concepts/abramson-cancer-center.html +++ b/concepts/abramson-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/abraxane.html b/concepts/abraxane.html index adcc1569f6..a5c2e3b831 100644 --- a/concepts/abraxane.html +++ b/concepts/abraxane.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/acd-au.html b/concepts/acd-au.html index a2a20d7b56..55938aa036 100644 --- a/concepts/acd-au.html +++ b/concepts/acd-au.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/acinar-cell-carcinoma-nos.html b/concepts/acinar-cell-carcinoma-nos.html index 0ca12255a7..4fbc78aca1 100644 --- a/concepts/acinar-cell-carcinoma-nos.html +++ b/concepts/acinar-cell-carcinoma-nos.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/acinar-cell-carcinoma-of-the-pancreas.html b/concepts/acinar-cell-carcinoma-of-the-pancreas.html index 49b2edd299..7496033a43 100644 --- a/concepts/acinar-cell-carcinoma-of-the-pancreas.html +++ b/concepts/acinar-cell-carcinoma-of-the-pancreas.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/acinic-cell-carcinoma.html b/concepts/acinic-cell-carcinoma.html index 772f9b14ea..2115527b5d 100644 --- a/concepts/acinic-cell-carcinoma.html +++ b/concepts/acinic-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/acral-melanoma.html b/concepts/acral-melanoma.html index 38ca5be0dd..5412891f56 100644 --- a/concepts/acral-melanoma.html +++ b/concepts/acral-melanoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/acs-can.html b/concepts/acs-can.html index 23cfdc83b2..0194fc5ff2 100644 --- a/concepts/acs-can.html +++ b/concepts/acs-can.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/acs-cancer-programs.html b/concepts/acs-cancer-programs.html index 0cf0a4808e..da893f915a 100644 --- a/concepts/acs-cancer-programs.html +++ b/concepts/acs-cancer-programs.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/acs.html b/concepts/acs.html index c9726bd7c1..912f6bba7b 100644 --- a/concepts/acs.html +++ b/concepts/acs.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/actcr-au.html b/concepts/actcr-au.html index 552bdec319..9af26204da 100644 --- a/concepts/actcr-au.html +++ b/concepts/actcr-au.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/actinomycin.html b/concepts/actinomycin.html index 0c36f9ecdc..05b9b42be2 100644 --- a/concepts/actinomycin.html +++ b/concepts/actinomycin.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/activated-b-cell-type.html b/concepts/activated-b-cell-type.html index a93b4bbd31..da160d0d43 100644 --- a/concepts/activated-b-cell-type.html +++ b/concepts/activated-b-cell-type.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/acupressure.html b/concepts/acupressure.html index b4f27cae62..801869eb62 100644 --- a/concepts/acupressure.html +++ b/concepts/acupressure.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/acupuncture.html b/concepts/acupuncture.html index 59115ad030..db8d79ecd0 100644 --- a/concepts/acupuncture.html +++ b/concepts/acupuncture.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/acute-basophilic-leukemia.html b/concepts/acute-basophilic-leukemia.html index a31006998a..4baf3ee07c 100644 --- a/concepts/acute-basophilic-leukemia.html +++ b/concepts/acute-basophilic-leukemia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/acute-leukemias-of-ambiguous-lineage.html b/concepts/acute-leukemias-of-ambiguous-lineage.html index 5370aa6f54..139274d937 100644 --- a/concepts/acute-leukemias-of-ambiguous-lineage.html +++ b/concepts/acute-leukemias-of-ambiguous-lineage.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/acute-megakaryoblastic-leukemia.html b/concepts/acute-megakaryoblastic-leukemia.html index 6e7ce2518d..4907c640d4 100644 --- a/concepts/acute-megakaryoblastic-leukemia.html +++ b/concepts/acute-megakaryoblastic-leukemia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/acute-monoblastic-monocytic-leukemia.html b/concepts/acute-monoblastic-monocytic-leukemia.html index 6c01ada997..4933434621 100644 --- a/concepts/acute-monoblastic-monocytic-leukemia.html +++ b/concepts/acute-monoblastic-monocytic-leukemia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/acute-myeloid-leukemia.html b/concepts/acute-myeloid-leukemia.html index ac5596d445..c93e3192a6 100644 --- a/concepts/acute-myeloid-leukemia.html +++ b/concepts/acute-myeloid-leukemia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/acute-myelomonocytic-leukemia.html b/concepts/acute-myelomonocytic-leukemia.html index cd80825689..3140538b6e 100644 --- a/concepts/acute-myelomonocytic-leukemia.html +++ b/concepts/acute-myelomonocytic-leukemia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/acute-panmyelosis-with-myelofibrosis.html b/concepts/acute-panmyelosis-with-myelofibrosis.html index 6bb8a7f304..a20d76ec15 100644 --- a/concepts/acute-panmyelosis-with-myelofibrosis.html +++ b/concepts/acute-panmyelosis-with-myelofibrosis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/acute-undifferentiated-leukemia.html b/concepts/acute-undifferentiated-leukemia.html index 67a1e7fa7e..192447bc59 100644 --- a/concepts/acute-undifferentiated-leukemia.html +++ b/concepts/acute-undifferentiated-leukemia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/adamantinoma.html b/concepts/adamantinoma.html index 18848ed995..3f39fdb2d2 100644 --- a/concepts/adamantinoma.html +++ b/concepts/adamantinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/adenocarcinoma-in-situ.html b/concepts/adenocarcinoma-in-situ.html index 666fe70baf..6f03f2421a 100644 --- a/concepts/adenocarcinoma-in-situ.html +++ b/concepts/adenocarcinoma-in-situ.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/adenocarcinoma-nos.html b/concepts/adenocarcinoma-nos.html index a22daa2053..1b52a1394b 100644 --- a/concepts/adenocarcinoma-nos.html +++ b/concepts/adenocarcinoma-nos.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/adenocarcinoma-of-the-gastroesophageal-junction.html b/concepts/adenocarcinoma-of-the-gastroesophageal-junction.html index 7824dc2a87..24086306e3 100644 --- a/concepts/adenocarcinoma-of-the-gastroesophageal-junction.html +++ b/concepts/adenocarcinoma-of-the-gastroesophageal-junction.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/adenoid-cystic-breast-cancer.html b/concepts/adenoid-cystic-breast-cancer.html index 0b199a2f34..1826b7a649 100644 --- a/concepts/adenoid-cystic-breast-cancer.html +++ b/concepts/adenoid-cystic-breast-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/adenoid-cystic-carcinoma-of-the-lung.html b/concepts/adenoid-cystic-carcinoma-of-the-lung.html index 7b06dd2112..d148d621ad 100644 --- a/concepts/adenoid-cystic-carcinoma-of-the-lung.html +++ b/concepts/adenoid-cystic-carcinoma-of-the-lung.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/adenoid-cystic-carcinoma.html b/concepts/adenoid-cystic-carcinoma.html index 4aab557751..cb2b8bf65f 100644 --- a/concepts/adenoid-cystic-carcinoma.html +++ b/concepts/adenoid-cystic-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/adenomyoepithelioma-of-the-breast.html b/concepts/adenomyoepithelioma-of-the-breast.html index bb500fa595..e96e059d02 100644 --- a/concepts/adenomyoepithelioma-of-the-breast.html +++ b/concepts/adenomyoepithelioma-of-the-breast.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/adenosquamous-carcinoma-of-the-gallbladder.html b/concepts/adenosquamous-carcinoma-of-the-gallbladder.html index f2feb51a4c..7d5da24916 100644 --- a/concepts/adenosquamous-carcinoma-of-the-gallbladder.html +++ b/concepts/adenosquamous-carcinoma-of-the-gallbladder.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/adenosquamous-carcinoma-of-the-pancreas.html b/concepts/adenosquamous-carcinoma-of-the-pancreas.html index f5e037ab66..d4d753ef2a 100644 --- a/concepts/adenosquamous-carcinoma-of-the-pancreas.html +++ b/concepts/adenosquamous-carcinoma-of-the-pancreas.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/adenosquamous-carcinoma-of-the-stomach.html b/concepts/adenosquamous-carcinoma-of-the-stomach.html index ac90c8ff0c..c782b17235 100644 --- a/concepts/adenosquamous-carcinoma-of-the-stomach.html +++ b/concepts/adenosquamous-carcinoma-of-the-stomach.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/adenosquamous-carcinoma-of-the-tongue.html b/concepts/adenosquamous-carcinoma-of-the-tongue.html index e650fbf065..0d0bd8f81d 100644 --- a/concepts/adenosquamous-carcinoma-of-the-tongue.html +++ b/concepts/adenosquamous-carcinoma-of-the-tongue.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/adrenal-gland.html b/concepts/adrenal-gland.html index ff78341fdd..f5eef31045 100644 --- a/concepts/adrenal-gland.html +++ b/concepts/adrenal-gland.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/adrenalectomy.html b/concepts/adrenalectomy.html index 5320c721ce..c795e5846f 100644 --- a/concepts/adrenalectomy.html +++ b/concepts/adrenalectomy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/adrenocortical-adenoma.html b/concepts/adrenocortical-adenoma.html index 7db65a85d7..d86f399406 100644 --- a/concepts/adrenocortical-adenoma.html +++ b/concepts/adrenocortical-adenoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/adrenocortical-carcinoma.html b/concepts/adrenocortical-carcinoma.html index 7db1ff9942..964a0a5bd2 100644 --- a/concepts/adrenocortical-carcinoma.html +++ b/concepts/adrenocortical-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/adult-t-cell-leukemia-lymphoma.html b/concepts/adult-t-cell-leukemia-lymphoma.html index 4873c1dc99..d4c81dd780 100644 --- a/concepts/adult-t-cell-leukemia-lymphoma.html +++ b/concepts/adult-t-cell-leukemia-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/advent-health-orlando.html b/concepts/advent-health-orlando.html index 3179c0f7dd..7da8f9eba1 100644 --- a/concepts/advent-health-orlando.html +++ b/concepts/advent-health-orlando.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/aecc.html b/concepts/aecc.html index 7ed631033c..3ef74967f9 100644 --- a/concepts/aecc.html +++ b/concepts/aecc.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/aggressive-angiomyxoma.html b/concepts/aggressive-angiomyxoma.html index 78604826f2..7867156962 100644 --- a/concepts/aggressive-angiomyxoma.html +++ b/concepts/aggressive-angiomyxoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/aggressive-digital-papillary-adenocarcinoma.html b/concepts/aggressive-digital-papillary-adenocarcinoma.html index 73f767ba0b..f9276873b0 100644 --- a/concepts/aggressive-digital-papillary-adenocarcinoma.html +++ b/concepts/aggressive-digital-papillary-adenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/aggressive-nk-cell-leukemia.html b/concepts/aggressive-nk-cell-leukemia.html index 9a2d92b9dc..2a357e98a4 100644 --- a/concepts/aggressive-nk-cell-leukemia.html +++ b/concepts/aggressive-nk-cell-leukemia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/aggressive-systemic-mastocytosis.html b/concepts/aggressive-systemic-mastocytosis.html index 5d6e2b1abe..69d0b87910 100644 --- a/concepts/aggressive-systemic-mastocytosis.html +++ b/concepts/aggressive-systemic-mastocytosis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/aicr.html b/concepts/aicr.html index ebe884e10c..bbd394abf7 100644 --- a/concepts/aicr.html +++ b/concepts/aicr.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/albert-b-chandler-hospital.html b/concepts/albert-b-chandler-hospital.html index fdd6bddbda..4209c16b89 100644 --- a/concepts/albert-b-chandler-hospital.html +++ b/concepts/albert-b-chandler-hospital.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/alitretinoin.html b/concepts/alitretinoin.html index cd49ee482f..2376740881 100644 --- a/concepts/alitretinoin.html +++ b/concepts/alitretinoin.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/alk-positive-large-b-cell-lymphoma.html b/concepts/alk-positive-large-b-cell-lymphoma.html index 5b4d96fd56..f86095a19d 100644 --- a/concepts/alk-positive-large-b-cell-lymphoma.html +++ b/concepts/alk-positive-large-b-cell-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/alkaline-diet.html b/concepts/alkaline-diet.html index 52c650109a..dd907011fb 100644 --- a/concepts/alkaline-diet.html +++ b/concepts/alkaline-diet.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/all.html b/concepts/all.html index 2b16c1879c..6877aec50a 100644 --- a/concepts/all.html +++ b/concepts/all.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/alpha-heavy-chain-disease.html b/concepts/alpha-heavy-chain-disease.html index b0e7b75325..9826d4b5ca 100644 --- a/concepts/alpha-heavy-chain-disease.html +++ b/concepts/alpha-heavy-chain-disease.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/altretamine.html b/concepts/altretamine.html index 707e6afe46..9603d60b5c 100644 --- a/concepts/altretamine.html +++ b/concepts/altretamine.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/alveolar-rhabdomyosarcoma.html b/concepts/alveolar-rhabdomyosarcoma.html index f395bb1da1..1a99c0f326 100644 --- a/concepts/alveolar-rhabdomyosarcoma.html +++ b/concepts/alveolar-rhabdomyosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/alveolar-soft-part-sarcoma.html b/concepts/alveolar-soft-part-sarcoma.html index e496ba36db..de0efd85b8 100644 --- a/concepts/alveolar-soft-part-sarcoma.html +++ b/concepts/alveolar-soft-part-sarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/alvin-j-siteman-cancer-center.html b/concepts/alvin-j-siteman-cancer-center.html index ef5afa4ab9..88fe335a81 100644 --- a/concepts/alvin-j-siteman-cancer-center.html +++ b/concepts/alvin-j-siteman-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/american-cancer-society.html b/concepts/american-cancer-society.html index d3cd31715d..cd32540304 100644 --- a/concepts/american-cancer-society.html +++ b/concepts/american-cancer-society.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/american-liver-foundation.html b/concepts/american-liver-foundation.html index c82f5615d4..5e7b5c0f7a 100644 --- a/concepts/american-liver-foundation.html +++ b/concepts/american-liver-foundation.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/american-lung-association.html b/concepts/american-lung-association.html index 641b736b54..c27467b69d 100644 --- a/concepts/american-lung-association.html +++ b/concepts/american-lung-association.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/amgen.html b/concepts/amgen.html index 3e620c9650..40a34c4382 100644 --- a/concepts/amgen.html +++ b/concepts/amgen.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/aml-megakaryoblastic-with-t122p13.3q13.3rbm15-mkl1.html b/concepts/aml-megakaryoblastic-with-t122p13.3q13.3rbm15-mkl1.html index b4acfc1e74..3a6ced9ad5 100644 --- a/concepts/aml-megakaryoblastic-with-t122p13.3q13.3rbm15-mkl1.html +++ b/concepts/aml-megakaryoblastic-with-t122p13.3q13.3rbm15-mkl1.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/aml-nos.html b/concepts/aml-nos.html index 3767bb1440..dd5fd03eb0 100644 --- a/concepts/aml-nos.html +++ b/concepts/aml-nos.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/aml-with-bcr-abl1.html b/concepts/aml-with-bcr-abl1.html index 7dd7aa9df2..bd72c831ff 100644 --- a/concepts/aml-with-bcr-abl1.html +++ b/concepts/aml-with-bcr-abl1.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/aml-with-biallelic-mutations-of-cebpa.html b/concepts/aml-with-biallelic-mutations-of-cebpa.html index 0e021d4500..85bb8c8ec2 100644 --- a/concepts/aml-with-biallelic-mutations-of-cebpa.html +++ b/concepts/aml-with-biallelic-mutations-of-cebpa.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/aml-with-inv16p13.1q22-or-t1616p13.1q22cbfb-myh11.html b/concepts/aml-with-inv16p13.1q22-or-t1616p13.1q22cbfb-myh11.html index 3a225f8689..38c78ca291 100644 --- a/concepts/aml-with-inv16p13.1q22-or-t1616p13.1q22cbfb-myh11.html +++ b/concepts/aml-with-inv16p13.1q22-or-t1616p13.1q22cbfb-myh11.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/aml-with-inv3q21.3q26.2-or-t33q21.3q26.2-gata2-mecom.html b/concepts/aml-with-inv3q21.3q26.2-or-t33q21.3q26.2-gata2-mecom.html index d5a1d92106..1f164f4c8c 100644 --- a/concepts/aml-with-inv3q21.3q26.2-or-t33q21.3q26.2-gata2-mecom.html +++ b/concepts/aml-with-inv3q21.3q26.2-or-t33q21.3q26.2-gata2-mecom.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/aml-with-maturation.html b/concepts/aml-with-maturation.html index d3a124bb3c..a93bad67ec 100644 --- a/concepts/aml-with-maturation.html +++ b/concepts/aml-with-maturation.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/aml-with-minimal-differentiation.html b/concepts/aml-with-minimal-differentiation.html index 33847b7dbd..3508ef83f3 100644 --- a/concepts/aml-with-minimal-differentiation.html +++ b/concepts/aml-with-minimal-differentiation.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/aml-with-mutated-npm1.html b/concepts/aml-with-mutated-npm1.html index 73de98ffef..22ae7e97e7 100644 --- a/concepts/aml-with-mutated-npm1.html +++ b/concepts/aml-with-mutated-npm1.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/aml-with-mutated-runx1.html b/concepts/aml-with-mutated-runx1.html index ec95e1d557..19cc56af8d 100644 --- a/concepts/aml-with-mutated-runx1.html +++ b/concepts/aml-with-mutated-runx1.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/aml-with-myelodysplasia-related-changes.html b/concepts/aml-with-myelodysplasia-related-changes.html index e053f20b4a..467aed278e 100644 --- a/concepts/aml-with-myelodysplasia-related-changes.html +++ b/concepts/aml-with-myelodysplasia-related-changes.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/aml-with-recurrent-genetic-abnormalities.html b/concepts/aml-with-recurrent-genetic-abnormalities.html index c3745fe05a..98d05044ed 100644 --- a/concepts/aml-with-recurrent-genetic-abnormalities.html +++ b/concepts/aml-with-recurrent-genetic-abnormalities.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/aml-with-t69p23q34.1dek-nup214.html b/concepts/aml-with-t69p23q34.1dek-nup214.html index 1332deec16..bd6c6b6039 100644 --- a/concepts/aml-with-t69p23q34.1dek-nup214.html +++ b/concepts/aml-with-t69p23q34.1dek-nup214.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/aml-with-t821q22q22.1runx1-runx1t1.html b/concepts/aml-with-t821q22q22.1runx1-runx1t1.html index 377bfad8d1..6e925e248a 100644 --- a/concepts/aml-with-t821q22q22.1runx1-runx1t1.html +++ b/concepts/aml-with-t821q22q22.1runx1-runx1t1.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/aml-with-t911p21.3q23.3mllt3-kmt2a.html b/concepts/aml-with-t911p21.3q23.3mllt3-kmt2a.html index b8da0913a5..a9926d6adb 100644 --- a/concepts/aml-with-t911p21.3q23.3mllt3-kmt2a.html +++ b/concepts/aml-with-t911p21.3q23.3mllt3-kmt2a.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/aml-without-maturation.html b/concepts/aml-without-maturation.html index 20c4e6e676..adf855a6fe 100644 --- a/concepts/aml-without-maturation.html +++ b/concepts/aml-without-maturation.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ampulla-of-vater.html b/concepts/ampulla-of-vater.html index c4c3a69688..5cb1fa9c52 100644 --- a/concepts/ampulla-of-vater.html +++ b/concepts/ampulla-of-vater.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ampullary-carcinoma.html b/concepts/ampullary-carcinoma.html index 2728f70887..ac87dbd7bc 100644 --- a/concepts/ampullary-carcinoma.html +++ b/concepts/ampullary-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/amyloidosis.html b/concepts/amyloidosis.html index 3b7c049ca8..45868f1e50 100644 --- a/concepts/amyloidosis.html +++ b/concepts/amyloidosis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/anal-gland-adenocarcinoma.html b/concepts/anal-gland-adenocarcinoma.html index 955176dae9..be3d898504 100644 --- a/concepts/anal-gland-adenocarcinoma.html +++ b/concepts/anal-gland-adenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/anal-squamous-cell-carcinoma.html b/concepts/anal-squamous-cell-carcinoma.html index 3eda505e1e..2deb1224fe 100644 --- a/concepts/anal-squamous-cell-carcinoma.html +++ b/concepts/anal-squamous-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/anaplastic-astrocytoma.html b/concepts/anaplastic-astrocytoma.html index f241af4fd8..2f3c6054c9 100644 --- a/concepts/anaplastic-astrocytoma.html +++ b/concepts/anaplastic-astrocytoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/anaplastic-ependymoma.html b/concepts/anaplastic-ependymoma.html index 0e0ad6e6c3..4ff8a648bf 100644 --- a/concepts/anaplastic-ependymoma.html +++ b/concepts/anaplastic-ependymoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/anaplastic-ganglioglioma.html b/concepts/anaplastic-ganglioglioma.html index 52b61d1442..538335c690 100644 --- a/concepts/anaplastic-ganglioglioma.html +++ b/concepts/anaplastic-ganglioglioma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/anaplastic-large-cell-lymphoma-alk-negative.html b/concepts/anaplastic-large-cell-lymphoma-alk-negative.html index 00270092a5..b8c0b91c92 100644 --- a/concepts/anaplastic-large-cell-lymphoma-alk-negative.html +++ b/concepts/anaplastic-large-cell-lymphoma-alk-negative.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/anaplastic-large-cell-lymphoma-alk-positive.html b/concepts/anaplastic-large-cell-lymphoma-alk-positive.html index 9fc8d2c93b..194b5261cb 100644 --- a/concepts/anaplastic-large-cell-lymphoma-alk-positive.html +++ b/concepts/anaplastic-large-cell-lymphoma-alk-positive.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/anaplastic-large-cell-lymphoma.html b/concepts/anaplastic-large-cell-lymphoma.html index eb92f6119f..9280e1d63d 100644 --- a/concepts/anaplastic-large-cell-lymphoma.html +++ b/concepts/anaplastic-large-cell-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/anaplastic-meningioma.html b/concepts/anaplastic-meningioma.html index 122122bb85..32c49e3cb5 100644 --- a/concepts/anaplastic-meningioma.html +++ b/concepts/anaplastic-meningioma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/anaplastic-oligoastrocytoma.html b/concepts/anaplastic-oligoastrocytoma.html index 79a932821f..5192685c3b 100644 --- a/concepts/anaplastic-oligoastrocytoma.html +++ b/concepts/anaplastic-oligoastrocytoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/anaplastic-oligodendroglioma.html b/concepts/anaplastic-oligodendroglioma.html index ac74374958..a7dd44d17b 100644 --- a/concepts/anaplastic-oligodendroglioma.html +++ b/concepts/anaplastic-oligodendroglioma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/anaplastic-pleomorphic-xanthoastrocytoma.html b/concepts/anaplastic-pleomorphic-xanthoastrocytoma.html index 6048ef25aa..379d3fec58 100644 --- a/concepts/anaplastic-pleomorphic-xanthoastrocytoma.html +++ b/concepts/anaplastic-pleomorphic-xanthoastrocytoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/anaplastic-thyroid-cancer.html b/concepts/anaplastic-thyroid-cancer.html index 57a60f3a83..88f03de41b 100644 --- a/concepts/anaplastic-thyroid-cancer.html +++ b/concepts/anaplastic-thyroid-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/angiocentric-glioma.html b/concepts/angiocentric-glioma.html index 96caa11c76..821162ba52 100644 --- a/concepts/angiocentric-glioma.html +++ b/concepts/angiocentric-glioma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/angioimmunoblastic-t-cell-lymphoma.html b/concepts/angioimmunoblastic-t-cell-lymphoma.html index 08f6bd5a19..50a9f46181 100644 --- a/concepts/angioimmunoblastic-t-cell-lymphoma.html +++ b/concepts/angioimmunoblastic-t-cell-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/angiomatoid-fibrous-histiocytoma.html b/concepts/angiomatoid-fibrous-histiocytoma.html index adfe48e6c9..f909cd3c73 100644 --- a/concepts/angiomatoid-fibrous-histiocytoma.html +++ b/concepts/angiomatoid-fibrous-histiocytoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/angiosarcoma.html b/concepts/angiosarcoma.html index 8bce6c6d1e..a86c914172 100644 --- a/concepts/angiosarcoma.html +++ b/concepts/angiosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/annals-of-oncology.html b/concepts/annals-of-oncology.html index dec66ec0b9..466cd28ea4 100644 --- a/concepts/annals-of-oncology.html +++ b/concepts/annals-of-oncology.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/anorectal-mucosal-melanoma.html b/concepts/anorectal-mucosal-melanoma.html index 04f55af0fa..12c0c96eb9 100644 --- a/concepts/anorectal-mucosal-melanoma.html +++ b/concepts/anorectal-mucosal-melanoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/anticancer.html b/concepts/anticancer.html index 785ec92328..fb2a7d469a 100644 --- a/concepts/anticancer.html +++ b/concepts/anticancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/apl-with-pml-rara.html b/concepts/apl-with-pml-rara.html index 1a9017807a..5a780466b7 100644 --- a/concepts/apl-with-pml-rara.html +++ b/concepts/apl-with-pml-rara.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/appendiceal-adenocarcinoma.html b/concepts/appendiceal-adenocarcinoma.html index 729c645055..43c73a3c9d 100644 --- a/concepts/appendiceal-adenocarcinoma.html +++ b/concepts/appendiceal-adenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/argentina-ncc.html b/concepts/argentina-ncc.html index 44419ce0c0..33458b3227 100644 --- a/concepts/argentina-ncc.html +++ b/concepts/argentina-ncc.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/arizona-cancer-center.html b/concepts/arizona-cancer-center.html index 7c9da87409..d848255495 100644 --- a/concepts/arizona-cancer-center.html +++ b/concepts/arizona-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/aromatherapy.html b/concepts/aromatherapy.html index ebe7db8680..842c4f40fe 100644 --- a/concepts/aromatherapy.html +++ b/concepts/aromatherapy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/arpha-h.html b/concepts/arpha-h.html index 83053ec7f6..507df36384 100644 --- a/concepts/arpha-h.html +++ b/concepts/arpha-h.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/asco.html b/concepts/asco.html index e9c4585a4d..b79b78cd52 100644 --- a/concepts/asco.html +++ b/concepts/asco.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/astrazeneca.html b/concepts/astrazeneca.html index d3c82e7c90..a78e1562c6 100644 --- a/concepts/astrazeneca.html +++ b/concepts/astrazeneca.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/astroblastoma.html b/concepts/astroblastoma.html index 2c808a96d6..3c6214bdef 100644 --- a/concepts/astroblastoma.html +++ b/concepts/astroblastoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/astrocytoma.html b/concepts/astrocytoma.html index 92dfcab908..2f43ebd75e 100644 --- a/concepts/astrocytoma.html +++ b/concepts/astrocytoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/atezolizumab.html b/concepts/atezolizumab.html index 3c6bb6e382..178ddbdcd0 100644 --- a/concepts/atezolizumab.html +++ b/concepts/atezolizumab.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/atypical-choroid-plexus-papilloma.html b/concepts/atypical-choroid-plexus-papilloma.html index d1156f2787..63e9e50b4f 100644 --- a/concepts/atypical-choroid-plexus-papilloma.html +++ b/concepts/atypical-choroid-plexus-papilloma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/atypical-chronic-myeloid-leukemia-bcr-abl1-.html b/concepts/atypical-chronic-myeloid-leukemia-bcr-abl1-.html index a4ef089521..53048e7097 100644 --- a/concepts/atypical-chronic-myeloid-leukemia-bcr-abl1-.html +++ b/concepts/atypical-chronic-myeloid-leukemia-bcr-abl1-.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/atypical-fibroxanthoma.html b/concepts/atypical-fibroxanthoma.html index bd6a07873d..a300e1ed4a 100644 --- a/concepts/atypical-fibroxanthoma.html +++ b/concepts/atypical-fibroxanthoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/atypical-lung-carcinoid.html b/concepts/atypical-lung-carcinoid.html index 686bc62c1e..dffcc9985d 100644 --- a/concepts/atypical-lung-carcinoid.html +++ b/concepts/atypical-lung-carcinoid.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/atypical-meningioma.html b/concepts/atypical-meningioma.html index 17000518a9..4cd859b35d 100644 --- a/concepts/atypical-meningioma.html +++ b/concepts/atypical-meningioma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/atypical-nevus.html b/concepts/atypical-nevus.html index 3151543a0e..8f07500bad 100644 --- a/concepts/atypical-nevus.html +++ b/concepts/atypical-nevus.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/atypical-pituitary-adenoma.html b/concepts/atypical-pituitary-adenoma.html index dc3fcb3ee0..8a620712e4 100644 --- a/concepts/atypical-pituitary-adenoma.html +++ b/concepts/atypical-pituitary-adenoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/atypical-teratoid-rhabdoid-tumor.html b/concepts/atypical-teratoid-rhabdoid-tumor.html index 2983479427..e8ceb879a0 100644 --- a/concepts/atypical-teratoid-rhabdoid-tumor.html +++ b/concepts/atypical-teratoid-rhabdoid-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/azacitidine.html b/concepts/azacitidine.html index 0a7e10e1d0..9e88db4bd0 100644 --- a/concepts/azacitidine.html +++ b/concepts/azacitidine.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/azathioprine.html b/concepts/azathioprine.html index 30ec94dc76..b2eb88d4ef 100644 --- a/concepts/azathioprine.html +++ b/concepts/azathioprine.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/b-cell-lymphoma-unclassifiable-with-features-intermediate-between-dlbcl-and-classical-hodgkin-lymphoma.html b/concepts/b-cell-lymphoma-unclassifiable-with-features-intermediate-between-dlbcl-and-classical-hodgkin-lymphoma.html index 8a96abb4d4..fe9b69724f 100644 --- a/concepts/b-cell-lymphoma-unclassifiable-with-features-intermediate-between-dlbcl-and-classical-hodgkin-lymphoma.html +++ b/concepts/b-cell-lymphoma-unclassifiable-with-features-intermediate-between-dlbcl-and-classical-hodgkin-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/b-cell-prolymphocytic-leukemia.html b/concepts/b-cell-prolymphocytic-leukemia.html index 94519a969a..c3695de110 100644 --- a/concepts/b-cell-prolymphocytic-leukemia.html +++ b/concepts/b-cell-prolymphocytic-leukemia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/b-lymphoblastic-leukemia-lymphoma-bcr-abl1-like.html b/concepts/b-lymphoblastic-leukemia-lymphoma-bcr-abl1-like.html index ec03c51dcc..21cc3b89cb 100644 --- a/concepts/b-lymphoblastic-leukemia-lymphoma-bcr-abl1-like.html +++ b/concepts/b-lymphoblastic-leukemia-lymphoma-bcr-abl1-like.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/b-lymphoblastic-leukemia-lymphoma-nos.html b/concepts/b-lymphoblastic-leukemia-lymphoma-nos.html index 4714715416..a2ff779618 100644 --- a/concepts/b-lymphoblastic-leukemia-lymphoma-nos.html +++ b/concepts/b-lymphoblastic-leukemia-lymphoma-nos.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/b-lymphoblastic-leukemia-lymphoma-with-hyperdiploidy.html b/concepts/b-lymphoblastic-leukemia-lymphoma-with-hyperdiploidy.html index aebcd4db88..fb82f5b6ec 100644 --- a/concepts/b-lymphoblastic-leukemia-lymphoma-with-hyperdiploidy.html +++ b/concepts/b-lymphoblastic-leukemia-lymphoma-with-hyperdiploidy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/b-lymphoblastic-leukemia-lymphoma-with-hypodiploidy.html b/concepts/b-lymphoblastic-leukemia-lymphoma-with-hypodiploidy.html index 6815c6940d..a4d368bf84 100644 --- a/concepts/b-lymphoblastic-leukemia-lymphoma-with-hypodiploidy.html +++ b/concepts/b-lymphoblastic-leukemia-lymphoma-with-hypodiploidy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/b-lymphoblastic-leukemia-lymphoma-with-iamp21.html b/concepts/b-lymphoblastic-leukemia-lymphoma-with-iamp21.html index 6fa1c37665..41821aa4a3 100644 --- a/concepts/b-lymphoblastic-leukemia-lymphoma-with-iamp21.html +++ b/concepts/b-lymphoblastic-leukemia-lymphoma-with-iamp21.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/b-lymphoblastic-leukemia-lymphoma-with-recurrent-genetic-abnormalities.html b/concepts/b-lymphoblastic-leukemia-lymphoma-with-recurrent-genetic-abnormalities.html index dcb0168f6e..1ed91c4943 100644 --- a/concepts/b-lymphoblastic-leukemia-lymphoma-with-recurrent-genetic-abnormalities.html +++ b/concepts/b-lymphoblastic-leukemia-lymphoma-with-recurrent-genetic-abnormalities.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/b-lymphoblastic-leukemia-lymphoma-with-t119q23p13.3tcf3-pbx1.html b/concepts/b-lymphoblastic-leukemia-lymphoma-with-t119q23p13.3tcf3-pbx1.html index 918190b541..985005ea47 100644 --- a/concepts/b-lymphoblastic-leukemia-lymphoma-with-t119q23p13.3tcf3-pbx1.html +++ b/concepts/b-lymphoblastic-leukemia-lymphoma-with-t119q23p13.3tcf3-pbx1.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/b-lymphoblastic-leukemia-lymphoma-with-t1221p13.2q22.1-etv6-runx1.html b/concepts/b-lymphoblastic-leukemia-lymphoma-with-t1221p13.2q22.1-etv6-runx1.html index 584752e062..52705b4e25 100644 --- a/concepts/b-lymphoblastic-leukemia-lymphoma-with-t1221p13.2q22.1-etv6-runx1.html +++ b/concepts/b-lymphoblastic-leukemia-lymphoma-with-t1221p13.2q22.1-etv6-runx1.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/b-lymphoblastic-leukemia-lymphoma-with-t514q31.1q32.3-il3-igh.html b/concepts/b-lymphoblastic-leukemia-lymphoma-with-t514q31.1q32.3-il3-igh.html index 59c295c45b..428d8ab7de 100644 --- a/concepts/b-lymphoblastic-leukemia-lymphoma-with-t514q31.1q32.3-il3-igh.html +++ b/concepts/b-lymphoblastic-leukemia-lymphoma-with-t514q31.1q32.3-il3-igh.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/b-lymphoblastic-leukemia-lymphoma-with-t922q34.1q11.2bcr-abl1.html b/concepts/b-lymphoblastic-leukemia-lymphoma-with-t922q34.1q11.2bcr-abl1.html index fb3a25426f..e916d3017c 100644 --- a/concepts/b-lymphoblastic-leukemia-lymphoma-with-t922q34.1q11.2bcr-abl1.html +++ b/concepts/b-lymphoblastic-leukemia-lymphoma-with-t922q34.1q11.2bcr-abl1.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/b-lymphoblastic-leukemia-lymphoma-with-tv11q23.3kmt2a-rearranged.html b/concepts/b-lymphoblastic-leukemia-lymphoma-with-tv11q23.3kmt2a-rearranged.html index 4f28f7fa3a..a01cdbc8e8 100644 --- a/concepts/b-lymphoblastic-leukemia-lymphoma-with-tv11q23.3kmt2a-rearranged.html +++ b/concepts/b-lymphoblastic-leukemia-lymphoma-with-tv11q23.3kmt2a-rearranged.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/b-lymphoblastic-leukemia-lymphoma.html b/concepts/b-lymphoblastic-leukemia-lymphoma.html index 0153a30e40..9dffa3489f 100644 --- a/concepts/b-lymphoblastic-leukemia-lymphoma.html +++ b/concepts/b-lymphoblastic-leukemia-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/basal-cell-carcinoma.html b/concepts/basal-cell-carcinoma.html index 5bdd959329..7d480e0e1b 100644 --- a/concepts/basal-cell-carcinoma.html +++ b/concepts/basal-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/basaloid-large-cell-carcinoma-of-the-lung.html b/concepts/basaloid-large-cell-carcinoma-of-the-lung.html index 331c9bd0d6..ae01accc84 100644 --- a/concepts/basaloid-large-cell-carcinoma-of-the-lung.html +++ b/concepts/basaloid-large-cell-carcinoma-of-the-lung.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/basaloid-penile-squamous-cell-carcinoma.html b/concepts/basaloid-penile-squamous-cell-carcinoma.html index 94f679072b..85a6cd4f6c 100644 --- a/concepts/basaloid-penile-squamous-cell-carcinoma.html +++ b/concepts/basaloid-penile-squamous-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/baxter.html b/concepts/baxter.html index c95257fc5b..2ce48d48a0 100644 --- a/concepts/baxter.html +++ b/concepts/baxter.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/bayer.html b/concepts/bayer.html index f56a679156..259817c3e2 100644 --- a/concepts/bayer.html +++ b/concepts/bayer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/bbcr-ar.html b/concepts/bbcr-ar.html index 70765d2368..3396652f66 100644 --- a/concepts/bbcr-ar.html +++ b/concepts/bbcr-ar.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/bccr-bn.html b/concepts/bccr-bn.html index 7b3f00bb04..e757874c86 100644 --- a/concepts/bccr-bn.html +++ b/concepts/bccr-bn.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/bcrisktool.html b/concepts/bcrisktool.html index 97b5395681..9b8cff64c5 100644 --- a/concepts/bcrisktool.html +++ b/concepts/bcrisktool.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/becton-dickinson-and-company.html b/concepts/becton-dickinson-and-company.html index a8606ad8aa..098ad6c85f 100644 --- a/concepts/becton-dickinson-and-company.html +++ b/concepts/becton-dickinson-and-company.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/beigene.html b/concepts/beigene.html index d11c629e1d..ecfde8ed54 100644 --- a/concepts/beigene.html +++ b/concepts/beigene.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/being-mortal-medicine-and-what-matters-in-the-end.html b/concepts/being-mortal-medicine-and-what-matters-in-the-end.html index d0611afe1b..39f9311d96 100644 --- a/concepts/being-mortal-medicine-and-what-matters-in-the-end.html +++ b/concepts/being-mortal-medicine-and-what-matters-in-the-end.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/belotecan.html b/concepts/belotecan.html index e5f3b461aa..50f801703f 100644 --- a/concepts/belotecan.html +++ b/concepts/belotecan.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/bendamustine.html b/concepts/bendamustine.html index 3c2f0f3654..2c1c8b09d7 100644 --- a/concepts/bendamustine.html +++ b/concepts/bendamustine.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/benign-phyllodes-tumor-of-the-breast.html b/concepts/benign-phyllodes-tumor-of-the-breast.html index eb07a2747b..7eb72bd57e 100644 --- a/concepts/benign-phyllodes-tumor-of-the-breast.html +++ b/concepts/benign-phyllodes-tumor-of-the-breast.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/beth-israel.html b/concepts/beth-israel.html index 69a2fb4818..41b313cfcc 100644 --- a/concepts/beth-israel.html +++ b/concepts/beth-israel.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/betrayed-by-nature-the-war-on-cancer.html b/concepts/betrayed-by-nature-the-war-on-cancer.html index 208cd8f11e..674b287bae 100644 --- a/concepts/betrayed-by-nature-the-war-on-cancer.html +++ b/concepts/betrayed-by-nature-the-war-on-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/bevacizumab.html b/concepts/bevacizumab.html index b7ab6a6330..1ce06f7110 100644 --- a/concepts/bevacizumab.html +++ b/concepts/bevacizumab.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/bexarotene.html b/concepts/bexarotene.html index b0b99b247e..6243e4bba3 100644 --- a/concepts/bexarotene.html +++ b/concepts/bexarotene.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/biliary-tract.html b/concepts/biliary-tract.html index 45d0a2cf54..775b98be6c 100644 --- a/concepts/biliary-tract.html +++ b/concepts/biliary-tract.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/biogen.html b/concepts/biogen.html index 4bc3e20dbf..c777feb374 100644 --- a/concepts/biogen.html +++ b/concepts/biogen.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/biontech.html b/concepts/biontech.html index 69e1c433a1..abd94be2be 100644 --- a/concepts/biontech.html +++ b/concepts/biontech.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/bladder-adenocarcinoma.html b/concepts/bladder-adenocarcinoma.html index 7f378d9897..c4779785e7 100644 --- a/concepts/bladder-adenocarcinoma.html +++ b/concepts/bladder-adenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/bladder-cancer-subreddit.html b/concepts/bladder-cancer-subreddit.html index 399e9831c0..7a80b98834 100644 --- a/concepts/bladder-cancer-subreddit.html +++ b/concepts/bladder-cancer-subreddit.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/bladder-cancer.html b/concepts/bladder-cancer.html index 7872a9763f..13f603e659 100644 --- a/concepts/bladder-cancer.html +++ b/concepts/bladder-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/bladder-squamous-cell-carcinoma.html b/concepts/bladder-squamous-cell-carcinoma.html index d294a17555..3a36cc4410 100644 --- a/concepts/bladder-squamous-cell-carcinoma.html +++ b/concepts/bladder-squamous-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/bladder-urothelial-carcinoma.html b/concepts/bladder-urothelial-carcinoma.html index b7ed123f5f..d5f6566661 100644 --- a/concepts/bladder-urothelial-carcinoma.html +++ b/concepts/bladder-urothelial-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/blastic-plasmacytoid-dendritic-cell-neoplasm.html b/concepts/blastic-plasmacytoid-dendritic-cell-neoplasm.html index 79d53fc5c9..6aa604fdf7 100644 --- a/concepts/blastic-plasmacytoid-dendritic-cell-neoplasm.html +++ b/concepts/blastic-plasmacytoid-dendritic-cell-neoplasm.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/bleomycin.html b/concepts/bleomycin.html index c5aa02d1d7..752179aabf 100644 --- a/concepts/bleomycin.html +++ b/concepts/bleomycin.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/bncr-bt.html b/concepts/bncr-bt.html index 6df2ddd0b6..cf7dd4dc6c 100644 --- a/concepts/bncr-bt.html +++ b/concepts/bncr-bt.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/boehringer-ingelheim.html b/concepts/boehringer-ingelheim.html index 6c287aeb10..e342b00364 100644 --- a/concepts/boehringer-ingelheim.html +++ b/concepts/boehringer-ingelheim.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/bone-marrow-and-cancer-foundation.html b/concepts/bone-marrow-and-cancer-foundation.html index e773ce7801..a992580436 100644 --- a/concepts/bone-marrow-and-cancer-foundation.html +++ b/concepts/bone-marrow-and-cancer-foundation.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/bone.html b/concepts/bone.html index c82f74327b..e16e3897f7 100644 --- a/concepts/bone.html +++ b/concepts/bone.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/borderline-phyllodes-tumor-of-the-breast.html b/concepts/borderline-phyllodes-tumor-of-the-breast.html index e957b17483..7219703b87 100644 --- a/concepts/borderline-phyllodes-tumor-of-the-breast.html +++ b/concepts/borderline-phyllodes-tumor-of-the-breast.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/bortezomib.html b/concepts/bortezomib.html index ffaa7a560c..dae9b7558e 100644 --- a/concepts/bortezomib.html +++ b/concepts/bortezomib.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/brachytherapy.html b/concepts/brachytherapy.html index 83107a7919..c9e3e249e4 100644 --- a/concepts/brachytherapy.html +++ b/concepts/brachytherapy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/brain.html b/concepts/brain.html index d515845c52..bdf68d08d3 100644 --- a/concepts/brain.html +++ b/concepts/brain.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/breast-angiosarcoma.html b/concepts/breast-angiosarcoma.html index 230d198a27..3d86aa8fef 100644 --- a/concepts/breast-angiosarcoma.html +++ b/concepts/breast-angiosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/breast-book.html b/concepts/breast-book.html index fc44ffd130..21283096b2 100644 --- a/concepts/breast-book.html +++ b/concepts/breast-book.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/breast-cancer-awareness-month.html b/concepts/breast-cancer-awareness-month.html index 9dc3d2f77c..b58bc3383d 100644 --- a/concepts/breast-cancer-awareness-month.html +++ b/concepts/breast-cancer-awareness-month.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/breast-cancer-subreddit.html b/concepts/breast-cancer-subreddit.html index 1772f79256..cae4ec0c95 100644 --- a/concepts/breast-cancer-subreddit.html +++ b/concepts/breast-cancer-subreddit.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/breast-carcinoma-with-signet-ring.html b/concepts/breast-carcinoma-with-signet-ring.html index 12d298e371..d83a3b99a2 100644 --- a/concepts/breast-carcinoma-with-signet-ring.html +++ b/concepts/breast-carcinoma-with-signet-ring.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/breast-ductal-carcinoma-in-situ.html b/concepts/breast-ductal-carcinoma-in-situ.html index 53670533f0..ab4b1e4a01 100644 --- a/concepts/breast-ductal-carcinoma-in-situ.html +++ b/concepts/breast-ductal-carcinoma-in-situ.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/breast-fibroepithelial-neoplasms.html b/concepts/breast-fibroepithelial-neoplasms.html index 00eaec21d3..ea7554843a 100644 --- a/concepts/breast-fibroepithelial-neoplasms.html +++ b/concepts/breast-fibroepithelial-neoplasms.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/breast-implant-associated-anaplastic-large-cell-lymphoma.html b/concepts/breast-implant-associated-anaplastic-large-cell-lymphoma.html index c148848ef1..a74cc01349 100644 --- a/concepts/breast-implant-associated-anaplastic-large-cell-lymphoma.html +++ b/concepts/breast-implant-associated-anaplastic-large-cell-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/breast-invasive-cancer-nos.html b/concepts/breast-invasive-cancer-nos.html index 063b98b598..5f1fc7aa9e 100644 --- a/concepts/breast-invasive-cancer-nos.html +++ b/concepts/breast-invasive-cancer-nos.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/breast-invasive-carcinoma-nos.html b/concepts/breast-invasive-carcinoma-nos.html index 8b3fa489e6..ec80fa6198 100644 --- a/concepts/breast-invasive-carcinoma-nos.html +++ b/concepts/breast-invasive-carcinoma-nos.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/breast-invasive-carcinosarcoma-nos.html b/concepts/breast-invasive-carcinosarcoma-nos.html index 82fe4bf4e9..bacdd0adf6 100644 --- a/concepts/breast-invasive-carcinosarcoma-nos.html +++ b/concepts/breast-invasive-carcinosarcoma-nos.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/breast-invasive-ductal-carcinoma.html b/concepts/breast-invasive-ductal-carcinoma.html index 3c568d118e..e8b8adf2a4 100644 --- a/concepts/breast-invasive-ductal-carcinoma.html +++ b/concepts/breast-invasive-ductal-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/breast-invasive-lobular-carcinoma.html b/concepts/breast-invasive-lobular-carcinoma.html index 1e672f90fd..3e709f5ea6 100644 --- a/concepts/breast-invasive-lobular-carcinoma.html +++ b/concepts/breast-invasive-lobular-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/breast-invasive-mixed-mucinous-carcinoma.html b/concepts/breast-invasive-mixed-mucinous-carcinoma.html index b6a3d7c27e..bc8b4dac23 100644 --- a/concepts/breast-invasive-mixed-mucinous-carcinoma.html +++ b/concepts/breast-invasive-mixed-mucinous-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/breast-lobular-carcinoma-in-situ.html b/concepts/breast-lobular-carcinoma-in-situ.html index 5ce02921d2..8cb8da56a2 100644 --- a/concepts/breast-lobular-carcinoma-in-situ.html +++ b/concepts/breast-lobular-carcinoma-in-situ.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/breast-mixed-ductal-and-lobular-carcinoma.html b/concepts/breast-mixed-ductal-and-lobular-carcinoma.html index 7452a9b475..92af151d1e 100644 --- a/concepts/breast-mixed-ductal-and-lobular-carcinoma.html +++ b/concepts/breast-mixed-ductal-and-lobular-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/breast-neoplasm-nos.html b/concepts/breast-neoplasm-nos.html index bafe1dded7..766296cd39 100644 --- a/concepts/breast-neoplasm-nos.html +++ b/concepts/breast-neoplasm-nos.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/breast-prostheses.html b/concepts/breast-prostheses.html index 1ab3f204e4..7c6adf54fc 100644 --- a/concepts/breast-prostheses.html +++ b/concepts/breast-prostheses.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/breast-sarcoma.html b/concepts/breast-sarcoma.html index 09f12db216..55b91fe33c 100644 --- a/concepts/breast-sarcoma.html +++ b/concepts/breast-sarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/breast.html b/concepts/breast.html index 904f511455..947c9db9c9 100644 --- a/concepts/breast.html +++ b/concepts/breast.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/breastcancer-dot-org.html b/concepts/breastcancer-dot-org.html index 7706f75bcf..44f6f58f28 100644 --- a/concepts/breastcancer-dot-org.html +++ b/concepts/breastcancer-dot-org.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/brenner-tumor-benign.html b/concepts/brenner-tumor-benign.html index 56cb40d5bd..b78564c716 100644 --- a/concepts/brenner-tumor-benign.html +++ b/concepts/brenner-tumor-benign.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/brenner-tumor-borderline.html b/concepts/brenner-tumor-borderline.html index 761e94dbd6..eefaca6880 100644 --- a/concepts/brenner-tumor-borderline.html +++ b/concepts/brenner-tumor-borderline.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/brenner-tumor-malignant.html b/concepts/brenner-tumor-malignant.html index 21c144f8e6..eb6d95b49b 100644 --- a/concepts/brenner-tumor-malignant.html +++ b/concepts/brenner-tumor-malignant.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/brenner-tumor.html b/concepts/brenner-tumor.html index 3853aa6b7a..ac9251cd79 100644 --- a/concepts/brenner-tumor.html +++ b/concepts/brenner-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/bristol-myers-squibb.html b/concepts/bristol-myers-squibb.html index ac6f538fbe..7b79b3bcc4 100644 --- a/concepts/bristol-myers-squibb.html +++ b/concepts/bristol-myers-squibb.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/burkitt-like-lymphoma-with-11q-aberration.html b/concepts/burkitt-like-lymphoma-with-11q-aberration.html index 02ae9ca137..10aad216c9 100644 --- a/concepts/burkitt-like-lymphoma-with-11q-aberration.html +++ b/concepts/burkitt-like-lymphoma-with-11q-aberration.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/burkitt-lymphoma.html b/concepts/burkitt-lymphoma.html index 6cea9cc9da..73794889e3 100644 --- a/concepts/burkitt-lymphoma.html +++ b/concepts/burkitt-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/busulfan.html b/concepts/busulfan.html index 9f5c3a8343..d16fa5d964 100644 --- a/concepts/busulfan.html +++ b/concepts/busulfan.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cabazitaxel.html b/concepts/cabazitaxel.html index a9ea7bb9e7..06906b013a 100644 --- a/concepts/cabazitaxel.html +++ b/concepts/cabazitaxel.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cabozantinib.html b/concepts/cabozantinib.html index d0917202d2..cd4c394463 100644 --- a/concepts/cabozantinib.html +++ b/concepts/cabozantinib.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/camptothecin.html b/concepts/camptothecin.html index cb262d0e06..07147eec46 100644 --- a/concepts/camptothecin.html +++ b/concepts/camptothecin.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/canada-ccm.html b/concepts/canada-ccm.html index 73d4c02117..5f001f7301 100644 --- a/concepts/canada-ccm.html +++ b/concepts/canada-ccm.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/canada-ohcc.html b/concepts/canada-ohcc.html index c1cc68cb1f..f7cde17935 100644 --- a/concepts/canada-ohcc.html +++ b/concepts/canada-ohcc.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/canadian-cancer-society.html b/concepts/canadian-cancer-society.html index 8f390b4471..cbf99c7a49 100644 --- a/concepts/canadian-cancer-society.html +++ b/concepts/canadian-cancer-society.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/canadian-partnership-against-cancer.html b/concepts/canadian-partnership-against-cancer.html index 5000d5acaf..4eef62f123 100644 --- a/concepts/canadian-partnership-against-cancer.html +++ b/concepts/canadian-partnership-against-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cancer-australia.html b/concepts/cancer-australia.html index 355f95d4ff..a585836cde 100644 --- a/concepts/cancer-australia.html +++ b/concepts/cancer-australia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cancer-cell-journal.html b/concepts/cancer-cell-journal.html index 694d0690cb..00d3850465 100644 --- a/concepts/cancer-cell-journal.html +++ b/concepts/cancer-cell-journal.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cancer-council-australia.html b/concepts/cancer-council-australia.html index 420c4c0924..0d1e0e096d 100644 --- a/concepts/cancer-council-australia.html +++ b/concepts/cancer-council-australia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cancer-crackdown.html b/concepts/cancer-crackdown.html index 76f956a1f8..8f604544c0 100644 --- a/concepts/cancer-crackdown.html +++ b/concepts/cancer-crackdown.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cancer-cured-victory-over-the-war-on-cancer-book.html b/concepts/cancer-cured-victory-over-the-war-on-cancer-book.html index 5bb1a89a90..833f93e56c 100644 --- a/concepts/cancer-cured-victory-over-the-war-on-cancer-book.html +++ b/concepts/cancer-cured-victory-over-the-war-on-cancer-book.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cancer-discovery.html b/concepts/cancer-discovery.html index 21f7ddbcac..822a6292c0 100644 --- a/concepts/cancer-discovery.html +++ b/concepts/cancer-discovery.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cancer-dot-net.html b/concepts/cancer-dot-net.html index a1c9a77933..97a3a87a14 100644 --- a/concepts/cancer-dot-net.html +++ b/concepts/cancer-dot-net.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cancer-is-not-a-disease-book.html b/concepts/cancer-is-not-a-disease-book.html index 99f8150f90..1df8c287c4 100644 --- a/concepts/cancer-is-not-a-disease-book.html +++ b/concepts/cancer-is-not-a-disease-book.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cancer-journal.html b/concepts/cancer-journal.html index 9256b13f5c..e94edabbf2 100644 --- a/concepts/cancer-journal.html +++ b/concepts/cancer-journal.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cancer-of-unknown-primary-nos.html b/concepts/cancer-of-unknown-primary-nos.html index 4c758dd312..25fd9faab3 100644 --- a/concepts/cancer-of-unknown-primary-nos.html +++ b/concepts/cancer-of-unknown-primary-nos.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cancer-of-unknown-primary.html b/concepts/cancer-of-unknown-primary.html index a17e256481..92325f257a 100644 --- a/concepts/cancer-of-unknown-primary.html +++ b/concepts/cancer-of-unknown-primary.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cancer-research-journal.html b/concepts/cancer-research-journal.html index 24a4e1fdf4..9896cf86de 100644 --- a/concepts/cancer-research-journal.html +++ b/concepts/cancer-research-journal.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cancer-research-uk.html b/concepts/cancer-research-uk.html index 592f99c4dd..967b004af4 100644 --- a/concepts/cancer-research-uk.html +++ b/concepts/cancer-research-uk.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cancer-risk-calculator.html b/concepts/cancer-risk-calculator.html index 32954a7ea2..237946c552 100644 --- a/concepts/cancer-risk-calculator.html +++ b/concepts/cancer-risk-calculator.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cancer-society-of-finland.html b/concepts/cancer-society-of-finland.html index 8772f30130..1db329ee0e 100644 --- a/concepts/cancer-society-of-finland.html +++ b/concepts/cancer-society-of-finland.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cancer-statistics-center.html b/concepts/cancer-statistics-center.html index b7f1fe8e66..8e5bfbfc4f 100644 --- a/concepts/cancer-statistics-center.html +++ b/concepts/cancer-statistics-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cancer-subreddit.html b/concepts/cancer-subreddit.html index 501886757d..f2e48badfb 100644 --- a/concepts/cancer-subreddit.html +++ b/concepts/cancer-subreddit.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cancer-support-community.html b/concepts/cancer-support-community.html index 8dad914c86..be9ba630e4 100644 --- a/concepts/cancer-support-community.html +++ b/concepts/cancer-support-community.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cancer-the-emperor-of-all-maladies.html b/concepts/cancer-the-emperor-of-all-maladies.html index fb50cc0c1f..b1dc023fac 100644 --- a/concepts/cancer-the-emperor-of-all-maladies.html +++ b/concepts/cancer-the-emperor-of-all-maladies.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cancer-today.html b/concepts/cancer-today.html index f0d1ddbe4f..02fc715ef0 100644 --- a/concepts/cancer-today.html +++ b/concepts/cancer-today.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cancer-ward.html b/concepts/cancer-ward.html index e58fe52cde..32e2af1c2f 100644 --- a/concepts/cancer-ward.html +++ b/concepts/cancer-ward.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cancerbuddy.html b/concepts/cancerbuddy.html index a372a7eb46..8727e576f7 100644 --- a/concepts/cancerbuddy.html +++ b/concepts/cancerbuddy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cancercare.html b/concepts/cancercare.html index 5108d31e33..fc778950cf 100644 --- a/concepts/cancercare.html +++ b/concepts/cancercare.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cancerdb.html b/concepts/cancerdb.html index f513f919a0..ad03b1b185 100644 --- a/concepts/cancerdb.html +++ b/concepts/cancerdb.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cancerfonden.html b/concepts/cancerfonden.html index dd6c4a35df..e6d46b9da2 100644 --- a/concepts/cancerfonden.html +++ b/concepts/cancerfonden.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/canswer.html b/concepts/canswer.html index 81ec5b5fbd..401ab83869 100644 --- a/concepts/canswer.html +++ b/concepts/canswer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/capecitabine.html b/concepts/capecitabine.html index 56eeaeeecc..0d23673112 100644 --- a/concepts/capecitabine.html +++ b/concepts/capecitabine.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/carboplatin.html b/concepts/carboplatin.html index 5b2f201746..220c130394 100644 --- a/concepts/carboplatin.html +++ b/concepts/carboplatin.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/carboquone.html b/concepts/carboquone.html index bacae57754..234156c773 100644 --- a/concepts/carboquone.html +++ b/concepts/carboquone.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/carcinoma-with-chondroid-metaplasia.html b/concepts/carcinoma-with-chondroid-metaplasia.html index 1562863d64..6a2959fb33 100644 --- a/concepts/carcinoma-with-chondroid-metaplasia.html +++ b/concepts/carcinoma-with-chondroid-metaplasia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/carcinoma-with-osseous-metaplasia.html b/concepts/carcinoma-with-osseous-metaplasia.html index a56a961021..37fd2b8a2d 100644 --- a/concepts/carcinoma-with-osseous-metaplasia.html +++ b/concepts/carcinoma-with-osseous-metaplasia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/carmustine.html b/concepts/carmustine.html index 1592d27fde..204c0313fb 100644 --- a/concepts/carmustine.html +++ b/concepts/carmustine.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/case-comprehensive-cancer-center.html b/concepts/case-comprehensive-cancer-center.html index f5a6151b0b..6ea65f5e06 100644 --- a/concepts/case-comprehensive-cancer-center.html +++ b/concepts/case-comprehensive-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cbioportal.html b/concepts/cbioportal.html index fccc721157..d9ad439386 100644 --- a/concepts/cbioportal.html +++ b/concepts/cbioportal.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ccan.html b/concepts/ccan.html index 89ed72ca6c..a02dbf9313 100644 --- a/concepts/ccan.html +++ b/concepts/ccan.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ccdi-annual-symposium.html b/concepts/ccdi-annual-symposium.html index 6e181fb882..ae88f50fb6 100644 --- a/concepts/ccdi-annual-symposium.html +++ b/concepts/ccdi-annual-symposium.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ccdi.html b/concepts/ccdi.html index 9333063fad..72103f6eb3 100644 --- a/concepts/ccdi.html +++ b/concepts/ccdi.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ccr-ar.html b/concepts/ccr-ar.html index ab83635ee6..dc5daa4bfb 100644 --- a/concepts/ccr-ar.html +++ b/concepts/ccr-ar.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ccrcal.html b/concepts/ccrcal.html index f173152dbf..7c62e52196 100644 --- a/concepts/ccrcal.html +++ b/concepts/ccrcal.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ccrisktool.html b/concepts/ccrisktool.html index 077a8fa30a..0de2ac09b1 100644 --- a/concepts/ccrisktool.html +++ b/concepts/ccrisktool.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ccrn-ng.html b/concepts/ccrn-ng.html index 730f222636..5f9c4b9bb7 100644 --- a/concepts/ccrn-ng.html +++ b/concepts/ccrn-ng.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cdc-division-of-cancer-prevention-and-control.html b/concepts/cdc-division-of-cancer-prevention-and-control.html index a9f12ac38b..618cdd2f61 100644 --- a/concepts/cdc-division-of-cancer-prevention-and-control.html +++ b/concepts/cdc-division-of-cancer-prevention-and-control.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cdc.html b/concepts/cdc.html index 023de6cb55..1b9e559cf2 100644 --- a/concepts/cdc.html +++ b/concepts/cdc.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cedars-sinai.html b/concepts/cedars-sinai.html index 35047530e3..a646798340 100644 --- a/concepts/cedars-sinai.html +++ b/concepts/cedars-sinai.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cellular-schwannoma.html b/concepts/cellular-schwannoma.html index 3086cdbaf8..d06c88692b 100644 --- a/concepts/cellular-schwannoma.html +++ b/concepts/cellular-schwannoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/center-for-molecular-oncology.html b/concepts/center-for-molecular-oncology.html index cddff2b2c1..326ce88962 100644 --- a/concepts/center-for-molecular-oncology.html +++ b/concepts/center-for-molecular-oncology.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/central-neurocytoma.html b/concepts/central-neurocytoma.html index d613c2c56d..a235661558 100644 --- a/concepts/central-neurocytoma.html +++ b/concepts/central-neurocytoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cerebellar-liponeurocytoma.html b/concepts/cerebellar-liponeurocytoma.html index da7d1c30b9..5bc84aa7a2 100644 --- a/concepts/cerebellar-liponeurocytoma.html +++ b/concepts/cerebellar-liponeurocytoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cervical-adenocarcinoma-in-situ.html b/concepts/cervical-adenocarcinoma-in-situ.html index ee337d286d..2161b66b94 100644 --- a/concepts/cervical-adenocarcinoma-in-situ.html +++ b/concepts/cervical-adenocarcinoma-in-situ.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cervical-adenocarcinoma.html b/concepts/cervical-adenocarcinoma.html index b19c2bf142..ee19e4d989 100644 --- a/concepts/cervical-adenocarcinoma.html +++ b/concepts/cervical-adenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cervical-adenoid-basal-carcinoma.html b/concepts/cervical-adenoid-basal-carcinoma.html index 77751872b8..a218abbfa4 100644 --- a/concepts/cervical-adenoid-basal-carcinoma.html +++ b/concepts/cervical-adenoid-basal-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cervical-adenoid-cystic-carcinoma.html b/concepts/cervical-adenoid-cystic-carcinoma.html index 9932f47e30..a7ba63f7fe 100644 --- a/concepts/cervical-adenoid-cystic-carcinoma.html +++ b/concepts/cervical-adenoid-cystic-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cervical-adenosquamous-carcinoma.html b/concepts/cervical-adenosquamous-carcinoma.html index d2bd18e5e9..5cae61d198 100644 --- a/concepts/cervical-adenosquamous-carcinoma.html +++ b/concepts/cervical-adenosquamous-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cervical-cancer-subreddit.html b/concepts/cervical-cancer-subreddit.html index 4152eb9ac5..4d296a494b 100644 --- a/concepts/cervical-cancer-subreddit.html +++ b/concepts/cervical-cancer-subreddit.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cervical-clear-cell-carcinoma.html b/concepts/cervical-clear-cell-carcinoma.html index 9defb39ca0..c4ad6f4cb3 100644 --- a/concepts/cervical-clear-cell-carcinoma.html +++ b/concepts/cervical-clear-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cervical-endometrioid-carcinoma.html b/concepts/cervical-endometrioid-carcinoma.html index 067307334a..f73724ee3f 100644 --- a/concepts/cervical-endometrioid-carcinoma.html +++ b/concepts/cervical-endometrioid-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cervical-leiomyosarcoma.html b/concepts/cervical-leiomyosarcoma.html index d41c5be9c9..884d0a1b3d 100644 --- a/concepts/cervical-leiomyosarcoma.html +++ b/concepts/cervical-leiomyosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cervical-neuroendocrine-tumor.html b/concepts/cervical-neuroendocrine-tumor.html index 8174f1dab6..800023975c 100644 --- a/concepts/cervical-neuroendocrine-tumor.html +++ b/concepts/cervical-neuroendocrine-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cervical-rhabdomyosarcoma.html b/concepts/cervical-rhabdomyosarcoma.html index bfbbc402b1..ecf60ae066 100644 --- a/concepts/cervical-rhabdomyosarcoma.html +++ b/concepts/cervical-rhabdomyosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cervical-serous-carcinoma.html b/concepts/cervical-serous-carcinoma.html index 161863826b..1dbaf86034 100644 --- a/concepts/cervical-serous-carcinoma.html +++ b/concepts/cervical-serous-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cervical-squamous-cell-carcinoma.html b/concepts/cervical-squamous-cell-carcinoma.html index e3f91b864a..ca7c622d80 100644 --- a/concepts/cervical-squamous-cell-carcinoma.html +++ b/concepts/cervical-squamous-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cervix.html b/concepts/cervix.html index 42e696ae37..31a35b76d4 100644 --- a/concepts/cervix.html +++ b/concepts/cervix.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/chao-family-comprehensive-cancer-center.html b/concepts/chao-family-comprehensive-cancer-center.html index 68a5bb2755..ffa2498067 100644 --- a/concepts/chao-family-comprehensive-cancer-center.html +++ b/concepts/chao-family-comprehensive-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/chaos-and-cancers-theories-concerning-carcinogene.html b/concepts/chaos-and-cancers-theories-concerning-carcinogene.html index 18739c4ec6..d1c1fbf028 100644 --- a/concepts/chaos-and-cancers-theories-concerning-carcinogene.html +++ b/concepts/chaos-and-cancers-theories-concerning-carcinogene.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/chlorambucil.html b/concepts/chlorambucil.html index 000441d48f..26b43d4456 100644 --- a/concepts/chlorambucil.html +++ b/concepts/chlorambucil.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/chlormethine.html b/concepts/chlormethine.html index 6a7774df71..8b1c7ab8a8 100644 --- a/concepts/chlormethine.html +++ b/concepts/chlormethine.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/chlorozotocin.html b/concepts/chlorozotocin.html index defb5e5335..2dc5791456 100644 --- a/concepts/chlorozotocin.html +++ b/concepts/chlorozotocin.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cholangiocarcinoma.html b/concepts/cholangiocarcinoma.html index 137e0f305c..33836c7887 100644 --- a/concepts/cholangiocarcinoma.html +++ b/concepts/cholangiocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/chondroblastic-osteosarcoma.html b/concepts/chondroblastic-osteosarcoma.html index dad5a3ca0e..c546216d88 100644 --- a/concepts/chondroblastic-osteosarcoma.html +++ b/concepts/chondroblastic-osteosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/chondroblastoma.html b/concepts/chondroblastoma.html index bec2edca87..ab9499a2e2 100644 --- a/concepts/chondroblastoma.html +++ b/concepts/chondroblastoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/chondrosarcoma.html b/concepts/chondrosarcoma.html index 60810be061..3f53960b86 100644 --- a/concepts/chondrosarcoma.html +++ b/concepts/chondrosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/chordoid-glioma-of-the-third-ventricle.html b/concepts/chordoid-glioma-of-the-third-ventricle.html index fdc51482a9..95981a75c9 100644 --- a/concepts/chordoid-glioma-of-the-third-ventricle.html +++ b/concepts/chordoid-glioma-of-the-third-ventricle.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/chordoid-meningioma.html b/concepts/chordoid-meningioma.html index 821c824e82..f8868c9938 100644 --- a/concepts/chordoid-meningioma.html +++ b/concepts/chordoid-meningioma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/chordoma.html b/concepts/chordoma.html index 550c729bff..9999ff0534 100644 --- a/concepts/chordoma.html +++ b/concepts/chordoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/choriocarcinoma-testis.html b/concepts/choriocarcinoma-testis.html index 890acfca32..829c846229 100644 --- a/concepts/choriocarcinoma-testis.html +++ b/concepts/choriocarcinoma-testis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/choriocarcinoma-uterus.html b/concepts/choriocarcinoma-uterus.html index cd87f0219d..dfe1428f0b 100644 --- a/concepts/choriocarcinoma-uterus.html +++ b/concepts/choriocarcinoma-uterus.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/choriocarcinoma.html b/concepts/choriocarcinoma.html index e89fbb0e73..8bdaa6fcc5 100644 --- a/concepts/choriocarcinoma.html +++ b/concepts/choriocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/choroid-plexus-carcinoma.html b/concepts/choroid-plexus-carcinoma.html index 99629923f4..78638056c8 100644 --- a/concepts/choroid-plexus-carcinoma.html +++ b/concepts/choroid-plexus-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/choroid-plexus-papilloma.html b/concepts/choroid-plexus-papilloma.html index ba894a365a..0c0d220342 100644 --- a/concepts/choroid-plexus-papilloma.html +++ b/concepts/choroid-plexus-papilloma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/choroid-plexus-tumor.html b/concepts/choroid-plexus-tumor.html index 04d2ce8bf5..75f9b36a16 100644 --- a/concepts/choroid-plexus-tumor.html +++ b/concepts/choroid-plexus-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/chris-beat-cancer.html b/concepts/chris-beat-cancer.html index d2aa6dd350..122094667d 100644 --- a/concepts/chris-beat-cancer.html +++ b/concepts/chris-beat-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/chromophobe-renal-cell-carcinoma.html b/concepts/chromophobe-renal-cell-carcinoma.html index 8483ebddb9..bf87e73959 100644 --- a/concepts/chromophobe-renal-cell-carcinoma.html +++ b/concepts/chromophobe-renal-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/chronic-eosinophilic-leukemia-nos.html b/concepts/chronic-eosinophilic-leukemia-nos.html index a4d0b69830..3e7388da99 100644 --- a/concepts/chronic-eosinophilic-leukemia-nos.html +++ b/concepts/chronic-eosinophilic-leukemia-nos.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/chronic-lymphocytic-leukemia-small-lymphocytic-lymphoma.html b/concepts/chronic-lymphocytic-leukemia-small-lymphocytic-lymphoma.html index f9002256bd..7b72140140 100644 --- a/concepts/chronic-lymphocytic-leukemia-small-lymphocytic-lymphoma.html +++ b/concepts/chronic-lymphocytic-leukemia-small-lymphocytic-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/chronic-lymphoproliferative-disorder-of-nk-cells.html b/concepts/chronic-lymphoproliferative-disorder-of-nk-cells.html index 3efcf96ed2..0efe1854e6 100644 --- a/concepts/chronic-lymphoproliferative-disorder-of-nk-cells.html +++ b/concepts/chronic-lymphoproliferative-disorder-of-nk-cells.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/chronic-myelogenous-leukemia.html b/concepts/chronic-myelogenous-leukemia.html index d7a6282827..dcf978f65b 100644 --- a/concepts/chronic-myelogenous-leukemia.html +++ b/concepts/chronic-myelogenous-leukemia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/chronic-myeloid-leukemia-bcr-abl1p.html b/concepts/chronic-myeloid-leukemia-bcr-abl1p.html index d8be724f8f..c83a70301c 100644 --- a/concepts/chronic-myeloid-leukemia-bcr-abl1p.html +++ b/concepts/chronic-myeloid-leukemia-bcr-abl1p.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/chronic-myelomonocytic-leukemia-0.html b/concepts/chronic-myelomonocytic-leukemia-0.html index 70b668efae..070dfa1bf6 100644 --- a/concepts/chronic-myelomonocytic-leukemia-0.html +++ b/concepts/chronic-myelomonocytic-leukemia-0.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/chronic-myelomonocytic-leukemia-1.html b/concepts/chronic-myelomonocytic-leukemia-1.html index 1a5073e8ab..439a906c30 100644 --- a/concepts/chronic-myelomonocytic-leukemia-1.html +++ b/concepts/chronic-myelomonocytic-leukemia-1.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/chronic-myelomonocytic-leukemia-2.html b/concepts/chronic-myelomonocytic-leukemia-2.html index 36f6809488..4e1b173719 100644 --- a/concepts/chronic-myelomonocytic-leukemia-2.html +++ b/concepts/chronic-myelomonocytic-leukemia-2.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/chronic-myelomonocytic-leukemia.html b/concepts/chronic-myelomonocytic-leukemia.html index 901035e97b..bcfa01ca80 100644 --- a/concepts/chronic-myelomonocytic-leukemia.html +++ b/concepts/chronic-myelomonocytic-leukemia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/chronic-neutrophilic-leukemia.html b/concepts/chronic-neutrophilic-leukemia.html index d8edce5281..6ac09291ac 100644 --- a/concepts/chronic-neutrophilic-leukemia.html +++ b/concepts/chronic-neutrophilic-leukemia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ciliated-muconodular-papillary-tumor-of-the-lung.html b/concepts/ciliated-muconodular-papillary-tumor-of-the-lung.html index 6ecdbd98a7..2f371a0fc5 100644 --- a/concepts/ciliated-muconodular-papillary-tumor-of-the-lung.html +++ b/concepts/ciliated-muconodular-papillary-tumor-of-the-lung.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cirm.html b/concepts/cirm.html index 83fdbdd267..b1c7e48fa1 100644 --- a/concepts/cirm.html +++ b/concepts/cirm.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cisplatin.html b/concepts/cisplatin.html index 2a23337a3e..0d840b9c4b 100644 --- a/concepts/cisplatin.html +++ b/concepts/cisplatin.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/city-of-hope-comprehensive-cancer-center.html b/concepts/city-of-hope-comprehensive-cancer-center.html index a87e82a79f..c7c45e8d1b 100644 --- a/concepts/city-of-hope-comprehensive-cancer-center.html +++ b/concepts/city-of-hope-comprehensive-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/city-of-hope-patient-and-family-education-library.html b/concepts/city-of-hope-patient-and-family-education-library.html index 5833ebe8e1..918ddedbf2 100644 --- a/concepts/city-of-hope-patient-and-family-education-library.html +++ b/concepts/city-of-hope-patient-and-family-education-library.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/classical-hodgkin-lymphoma-ptld.html b/concepts/classical-hodgkin-lymphoma-ptld.html index 4932cebdc6..87010b19e8 100644 --- a/concepts/classical-hodgkin-lymphoma-ptld.html +++ b/concepts/classical-hodgkin-lymphoma-ptld.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/classical-hodgkin-lymphoma.html b/concepts/classical-hodgkin-lymphoma.html index 3369588962..b83d2cf088 100644 --- a/concepts/classical-hodgkin-lymphoma.html +++ b/concepts/classical-hodgkin-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/clear-cell-borderline-ovarian-tumor.html b/concepts/clear-cell-borderline-ovarian-tumor.html index 87236bca60..61336baa47 100644 --- a/concepts/clear-cell-borderline-ovarian-tumor.html +++ b/concepts/clear-cell-borderline-ovarian-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/clear-cell-carcinoma-of-the-lung.html b/concepts/clear-cell-carcinoma-of-the-lung.html index b561393032..bd2d93bde8 100644 --- a/concepts/clear-cell-carcinoma-of-the-lung.html +++ b/concepts/clear-cell-carcinoma-of-the-lung.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/clear-cell-ependymoma.html b/concepts/clear-cell-ependymoma.html index 96cfabe588..7e6ac6174a 100644 --- a/concepts/clear-cell-ependymoma.html +++ b/concepts/clear-cell-ependymoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/clear-cell-meningioma.html b/concepts/clear-cell-meningioma.html index e29edaeb31..97525b72e1 100644 --- a/concepts/clear-cell-meningioma.html +++ b/concepts/clear-cell-meningioma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/clear-cell-odontogenic-carcinoma.html b/concepts/clear-cell-odontogenic-carcinoma.html index b2311ce6ff..652dfb308a 100644 --- a/concepts/clear-cell-odontogenic-carcinoma.html +++ b/concepts/clear-cell-odontogenic-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/clear-cell-ovarian-cancer.html b/concepts/clear-cell-ovarian-cancer.html index 73aa2433a1..d806d7d4ce 100644 --- a/concepts/clear-cell-ovarian-cancer.html +++ b/concepts/clear-cell-ovarian-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/clear-cell-papillary-renal-cell-carcinoma.html b/concepts/clear-cell-papillary-renal-cell-carcinoma.html index a2c901ab80..6107324f6b 100644 --- a/concepts/clear-cell-papillary-renal-cell-carcinoma.html +++ b/concepts/clear-cell-papillary-renal-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/clear-cell-sarcoma-of-kidney.html b/concepts/clear-cell-sarcoma-of-kidney.html index b184652239..aac19f5387 100644 --- a/concepts/clear-cell-sarcoma-of-kidney.html +++ b/concepts/clear-cell-sarcoma-of-kidney.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/clear-cell-sarcoma.html b/concepts/clear-cell-sarcoma.html index e8b761514c..02e239e4c9 100644 --- a/concepts/clear-cell-sarcoma.html +++ b/concepts/clear-cell-sarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cleveland-clinic.html b/concepts/cleveland-clinic.html index cab8c70bfa..ecf1eb7a74 100644 --- a/concepts/cleveland-clinic.html +++ b/concepts/cleveland-clinic.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/clinical-cancer-research.html b/concepts/clinical-cancer-research.html index 8ff0174e99..5ea78ff1f6 100644 --- a/concepts/clinical-cancer-research.html +++ b/concepts/clinical-cancer-research.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/clinical-trials-dot-gov.html b/concepts/clinical-trials-dot-gov.html index 2859c848a9..dea4402972 100644 --- a/concepts/clinical-trials-dot-gov.html +++ b/concepts/clinical-trials-dot-gov.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cms.html b/concepts/cms.html index e253fe4c0b..9bfd1c0ed6 100644 --- a/concepts/cms.html +++ b/concepts/cms.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cnio.html b/concepts/cnio.html index d2c280966f..14726d73ef 100644 --- a/concepts/cnio.html +++ b/concepts/cnio.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/coc.html b/concepts/coc.html index 68d75606c2..86b862bd7e 100644 --- a/concepts/coc.html +++ b/concepts/coc.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cocr-ar.html b/concepts/cocr-ar.html index ba5eec3dbe..5886f0ac9c 100644 --- a/concepts/cocr-ar.html +++ b/concepts/cocr-ar.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cognitive-behavioral-therapy.html b/concepts/cognitive-behavioral-therapy.html index b8403bf16f..ca22ff1b97 100644 --- a/concepts/cognitive-behavioral-therapy.html +++ b/concepts/cognitive-behavioral-therapy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cold-spring-harbor-laboratory-cancer-center.html b/concepts/cold-spring-harbor-laboratory-cancer-center.html index 1b10508418..69a0c0e926 100644 --- a/concepts/cold-spring-harbor-laboratory-cancer-center.html +++ b/concepts/cold-spring-harbor-laboratory-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/colectomy.html b/concepts/colectomy.html index 743faf5495..feac783b2e 100644 --- a/concepts/colectomy.html +++ b/concepts/colectomy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/collecting-duct-renal-cell-carcinoma.html b/concepts/collecting-duct-renal-cell-carcinoma.html index 8f01d43435..3f18c17909 100644 --- a/concepts/collecting-duct-renal-cell-carcinoma.html +++ b/concepts/collecting-duct-renal-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/college-of-american-pathologists.html b/concepts/college-of-american-pathologists.html index ab0e7471a4..3c92871100 100644 --- a/concepts/college-of-american-pathologists.html +++ b/concepts/college-of-american-pathologists.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/colon-adenocarcinoma-in-situ.html b/concepts/colon-adenocarcinoma-in-situ.html index 0ad61c828e..35547ef057 100644 --- a/concepts/colon-adenocarcinoma-in-situ.html +++ b/concepts/colon-adenocarcinoma-in-situ.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/colon-adenocarcinoma.html b/concepts/colon-adenocarcinoma.html index 8319561ffd..66f6504c5c 100644 --- a/concepts/colon-adenocarcinoma.html +++ b/concepts/colon-adenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/colon-cancer-subreddit.html b/concepts/colon-cancer-subreddit.html index 4cf2fe68e1..f8779910ef 100644 --- a/concepts/colon-cancer-subreddit.html +++ b/concepts/colon-cancer-subreddit.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/colon.html b/concepts/colon.html index 658479f6a0..3a71cf3726 100644 --- a/concepts/colon.html +++ b/concepts/colon.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/colonic-type-adenocarcinoma-of-the-appendix.html b/concepts/colonic-type-adenocarcinoma-of-the-appendix.html index 37ee158479..e91a894dc9 100644 --- a/concepts/colonic-type-adenocarcinoma-of-the-appendix.html +++ b/concepts/colonic-type-adenocarcinoma-of-the-appendix.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/colonoscopy.html b/concepts/colonoscopy.html index 2ba7270daf..eb02a1493e 100644 --- a/concepts/colonoscopy.html +++ b/concepts/colonoscopy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/colorectal-adenocarcinoma.html b/concepts/colorectal-adenocarcinoma.html index 7315ed7f5e..b23c472f1d 100644 --- a/concepts/colorectal-adenocarcinoma.html +++ b/concepts/colorectal-adenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/colorectal.html b/concepts/colorectal.html index ece9f2db84..1f2c00b7fa 100644 --- a/concepts/colorectal.html +++ b/concepts/colorectal.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/combined-small-cell-lung-carcinoma.html b/concepts/combined-small-cell-lung-carcinoma.html index 9a25ec3e8c..519d39335f 100644 --- a/concepts/combined-small-cell-lung-carcinoma.html +++ b/concepts/combined-small-cell-lung-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/complete-hydatidiform-mole.html b/concepts/complete-hydatidiform-mole.html index b3c16b3dde..badfe0ac10 100644 --- a/concepts/complete-hydatidiform-mole.html +++ b/concepts/complete-hydatidiform-mole.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/congenital-nevus.html b/concepts/congenital-nevus.html index 6b61930a16..9dbdafea28 100644 --- a/concepts/congenital-nevus.html +++ b/concepts/congenital-nevus.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/conjunctival-melanoma.html b/concepts/conjunctival-melanoma.html index 0438e11763..d6c70e633c 100644 --- a/concepts/conjunctival-melanoma.html +++ b/concepts/conjunctival-melanoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/conventional-type-chordoma.html b/concepts/conventional-type-chordoma.html index ee7f0a0e0e..75c88c9585 100644 --- a/concepts/conventional-type-chordoma.html +++ b/concepts/conventional-type-chordoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/craniopharyngioma-adamantinomatous-type.html b/concepts/craniopharyngioma-adamantinomatous-type.html index 161cee23c3..30bad23db7 100644 --- a/concepts/craniopharyngioma-adamantinomatous-type.html +++ b/concepts/craniopharyngioma-adamantinomatous-type.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/craniopharyngioma-papillary-type.html b/concepts/craniopharyngioma-papillary-type.html index 5b47458e37..ecd6d09b87 100644 --- a/concepts/craniopharyngioma-papillary-type.html +++ b/concepts/craniopharyngioma-papillary-type.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/crptdf-ar.html b/concepts/crptdf-ar.html index c3d112f78f..2080850aef 100644 --- a/concepts/crptdf-ar.html +++ b/concepts/crptdf-ar.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/crsba-dz.html b/concepts/crsba-dz.html index 1af37b3d94..92e7d980e1 100644 --- a/concepts/crsba-dz.html +++ b/concepts/crsba-dz.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/crwb-dz.html b/concepts/crwb-dz.html index 07c5bd107f..a3fc0ff021 100644 --- a/concepts/crwb-dz.html +++ b/concepts/crwb-dz.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cryoablation.html b/concepts/cryoablation.html index 09724d5fee..e813bdf96c 100644 --- a/concepts/cryoablation.html +++ b/concepts/cryoablation.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ct-colonoscopy.html b/concepts/ct-colonoscopy.html index 265fd86ef2..d5e2a5bfbd 100644 --- a/concepts/ct-colonoscopy.html +++ b/concepts/ct-colonoscopy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ctca.html b/concepts/ctca.html index d827fd14a7..b2860fe516 100644 --- a/concepts/ctca.html +++ b/concepts/ctca.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/curium-pharma.html b/concepts/curium-pharma.html index 87776fd4e8..0836c6f3f1 100644 --- a/concepts/curium-pharma.html +++ b/concepts/curium-pharma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cutaneous-mastocytosis.html b/concepts/cutaneous-mastocytosis.html index 88475cf989..ff421a6fec 100644 --- a/concepts/cutaneous-mastocytosis.html +++ b/concepts/cutaneous-mastocytosis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cutaneous-melanoma.html b/concepts/cutaneous-melanoma.html index be0ab4b02e..4e5ca985be 100644 --- a/concepts/cutaneous-melanoma.html +++ b/concepts/cutaneous-melanoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cutaneous-squamous-cell-carcinoma.html b/concepts/cutaneous-squamous-cell-carcinoma.html index e78bded146..3c80b3832e 100644 --- a/concepts/cutaneous-squamous-cell-carcinoma.html +++ b/concepts/cutaneous-squamous-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cyclophosphamide.html b/concepts/cyclophosphamide.html index a58681a0f6..2444a6464a 100644 --- a/concepts/cyclophosphamide.html +++ b/concepts/cyclophosphamide.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cystectomy.html b/concepts/cystectomy.html index 25a0ab2179..537141cdf0 100644 --- a/concepts/cystectomy.html +++ b/concepts/cystectomy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cystic-tumor-of-the-pancreas.html b/concepts/cystic-tumor-of-the-pancreas.html index f084377d9d..ecd292d76f 100644 --- a/concepts/cystic-tumor-of-the-pancreas.html +++ b/concepts/cystic-tumor-of-the-pancreas.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/cytarabine.html b/concepts/cytarabine.html index c64c25ce6d..02994320e6 100644 --- a/concepts/cytarabine.html +++ b/concepts/cytarabine.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/czechrepublic-ncc.html b/concepts/czechrepublic-ncc.html index 98741dda0e..767116c9fb 100644 --- a/concepts/czechrepublic-ncc.html +++ b/concepts/czechrepublic-ncc.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/dacarbazine.html b/concepts/dacarbazine.html index d71ffef380..daa083b28a 100644 --- a/concepts/dacarbazine.html +++ b/concepts/dacarbazine.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/daiichi-sankyo.html b/concepts/daiichi-sankyo.html index bb8ed4d3e4..54b047a1fd 100644 --- a/concepts/daiichi-sankyo.html +++ b/concepts/daiichi-sankyo.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/damon-runyon-cancer-research-foundation.html b/concepts/damon-runyon-cancer-research-foundation.html index c6c5d8b8d9..e427dd4e49 100644 --- a/concepts/damon-runyon-cancer-research-foundation.html +++ b/concepts/damon-runyon-cancer-research-foundation.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/dan-l-duncan-comprehensive-cancer-center.html b/concepts/dan-l-duncan-comprehensive-cancer-center.html index 602b8e7220..0250ccf088 100644 --- a/concepts/dan-l-duncan-comprehensive-cancer-center.html +++ b/concepts/dan-l-duncan-comprehensive-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/dana-farber-harvard-cancer-center.html b/concepts/dana-farber-harvard-cancer-center.html index 8a733b53d9..bc4be24e79 100644 --- a/concepts/dana-farber-harvard-cancer-center.html +++ b/concepts/dana-farber-harvard-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/dana-farber.html b/concepts/dana-farber.html index 94cd57852a..946692d447 100644 --- a/concepts/dana-farber.html +++ b/concepts/dana-farber.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/dartmouth-cancer-center.html b/concepts/dartmouth-cancer-center.html index 94c9af3ef9..39345550a2 100644 --- a/concepts/dartmouth-cancer-center.html +++ b/concepts/dartmouth-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/daunorubicin.html b/concepts/daunorubicin.html index 198e520a9d..1e50ec5332 100644 --- a/concepts/daunorubicin.html +++ b/concepts/daunorubicin.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/david-h-koch-institute-for-integrative-cancer-research-at-mit.html b/concepts/david-h-koch-institute-for-integrative-cancer-research-at-mit.html index 53bd69b89a..bfd00eb43e 100644 --- a/concepts/david-h-koch-institute-for-integrative-cancer-research-at-mit.html +++ b/concepts/david-h-koch-institute-for-integrative-cancer-research-at-mit.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/dedifferentiated-chondrosarcoma.html b/concepts/dedifferentiated-chondrosarcoma.html index 221485af94..bd2661c310 100644 --- a/concepts/dedifferentiated-chondrosarcoma.html +++ b/concepts/dedifferentiated-chondrosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/dedifferentiated-chordoma.html b/concepts/dedifferentiated-chordoma.html index ff5400db77..92e67d4647 100644 --- a/concepts/dedifferentiated-chordoma.html +++ b/concepts/dedifferentiated-chordoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/dedifferentiated-liposarcoma.html b/concepts/dedifferentiated-liposarcoma.html index 298827b12d..d21388b254 100644 --- a/concepts/dedifferentiated-liposarcoma.html +++ b/concepts/dedifferentiated-liposarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/dendritic-cell-sarcoma.html b/concepts/dendritic-cell-sarcoma.html index b346da3d11..d8d54fc6da 100644 --- a/concepts/dendritic-cell-sarcoma.html +++ b/concepts/dendritic-cell-sarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/dermatofibroma.html b/concepts/dermatofibroma.html index 1de18dd9d3..c1548925b7 100644 --- a/concepts/dermatofibroma.html +++ b/concepts/dermatofibroma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/dermatofibrosarcoma-protuberans.html b/concepts/dermatofibrosarcoma-protuberans.html index 6e61d83e6b..5e14127329 100644 --- a/concepts/dermatofibrosarcoma-protuberans.html +++ b/concepts/dermatofibrosarcoma-protuberans.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/desmoid-aggressive-fibromatosis.html b/concepts/desmoid-aggressive-fibromatosis.html index 5d029358fa..e79ad033dd 100644 --- a/concepts/desmoid-aggressive-fibromatosis.html +++ b/concepts/desmoid-aggressive-fibromatosis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/desmoplastic-infantile-astrocytoma.html b/concepts/desmoplastic-infantile-astrocytoma.html index ec788205b7..ae98043c47 100644 --- a/concepts/desmoplastic-infantile-astrocytoma.html +++ b/concepts/desmoplastic-infantile-astrocytoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/desmoplastic-infantile-ganglioglioma.html b/concepts/desmoplastic-infantile-ganglioglioma.html index 5548ad4512..03cf0d77e7 100644 --- a/concepts/desmoplastic-infantile-ganglioglioma.html +++ b/concepts/desmoplastic-infantile-ganglioglioma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/desmoplastic-melanoma.html b/concepts/desmoplastic-melanoma.html index 73c17110e8..461393f5a8 100644 --- a/concepts/desmoplastic-melanoma.html +++ b/concepts/desmoplastic-melanoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/desmoplastic-nodular-medulloblastoma.html b/concepts/desmoplastic-nodular-medulloblastoma.html index d4b76b409a..011aeba494 100644 --- a/concepts/desmoplastic-nodular-medulloblastoma.html +++ b/concepts/desmoplastic-nodular-medulloblastoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/desmoplastic-small-round-cell-tumor.html b/concepts/desmoplastic-small-round-cell-tumor.html index ceee6c97b3..ccd651a069 100644 --- a/concepts/desmoplastic-small-round-cell-tumor.html +++ b/concepts/desmoplastic-small-round-cell-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/desmoplastic-trichoepithelioma.html b/concepts/desmoplastic-trichoepithelioma.html index 2b9d382caa..8686c15678 100644 --- a/concepts/desmoplastic-trichoepithelioma.html +++ b/concepts/desmoplastic-trichoepithelioma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/dicycloplatin.html b/concepts/dicycloplatin.html index d93c0362f7..1231ab0e72 100644 --- a/concepts/dicycloplatin.html +++ b/concepts/dicycloplatin.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/diffuse-astrocytoma.html b/concepts/diffuse-astrocytoma.html index 2edc460e51..379eac9f35 100644 --- a/concepts/diffuse-astrocytoma.html +++ b/concepts/diffuse-astrocytoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/diffuse-glioma.html b/concepts/diffuse-glioma.html index fbf75aa76c..438d74dd09 100644 --- a/concepts/diffuse-glioma.html +++ b/concepts/diffuse-glioma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/diffuse-intrinsic-pontine-glioma.html b/concepts/diffuse-intrinsic-pontine-glioma.html index 835ee0f199..771c5d9995 100644 --- a/concepts/diffuse-intrinsic-pontine-glioma.html +++ b/concepts/diffuse-intrinsic-pontine-glioma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/diffuse-large-b-cell-lymphoma-nos.html b/concepts/diffuse-large-b-cell-lymphoma-nos.html index 8d1f8b46f4..69060923a1 100644 --- a/concepts/diffuse-large-b-cell-lymphoma-nos.html +++ b/concepts/diffuse-large-b-cell-lymphoma-nos.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/diffuse-type-stomach-adenocarcinoma.html b/concepts/diffuse-type-stomach-adenocarcinoma.html index 430514c35b..9f5d290df4 100644 --- a/concepts/diffuse-type-stomach-adenocarcinoma.html +++ b/concepts/diffuse-type-stomach-adenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/disseminated-juvenile-xanthogranuloma.html b/concepts/disseminated-juvenile-xanthogranuloma.html index 467fed1f3f..5bf1329d64 100644 --- a/concepts/disseminated-juvenile-xanthogranuloma.html +++ b/concepts/disseminated-juvenile-xanthogranuloma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/dlbcl-associated-with-chronic-inflammation.html b/concepts/dlbcl-associated-with-chronic-inflammation.html index a0e0af7dfd..a159b3db14 100644 --- a/concepts/dlbcl-associated-with-chronic-inflammation.html +++ b/concepts/dlbcl-associated-with-chronic-inflammation.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/docetaxel.html b/concepts/docetaxel.html index 434844e5e8..99e5791d68 100644 --- a/concepts/docetaxel.html +++ b/concepts/docetaxel.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/dominicanrepublic-ncc.html b/concepts/dominicanrepublic-ncc.html index adfbf4fee5..90fc86b023 100644 --- a/concepts/dominicanrepublic-ncc.html +++ b/concepts/dominicanrepublic-ncc.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/doxifluridine.html b/concepts/doxifluridine.html index e89dfc2632..151385dba3 100644 --- a/concepts/doxifluridine.html +++ b/concepts/doxifluridine.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/doxorubicin.html b/concepts/doxorubicin.html index 502ce6ea2b..5e07c5610e 100644 --- a/concepts/doxorubicin.html +++ b/concepts/doxorubicin.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/dr-sebi-cure-for-cancer.html b/concepts/dr-sebi-cure-for-cancer.html index 7bc6dd2d78..0ddcdedd50 100644 --- a/concepts/dr-sebi-cure-for-cancer.html +++ b/concepts/dr-sebi-cure-for-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/dta-dz.html b/concepts/dta-dz.html index 86e4de8b98..f9ea06ece1 100644 --- a/concepts/dta-dz.html +++ b/concepts/dta-dz.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/duke-cancer-institute.html b/concepts/duke-cancer-institute.html index 2c091f4351..4b11d75da3 100644 --- a/concepts/duke-cancer-institute.html +++ b/concepts/duke-cancer-institute.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/duodenal-adenocarcinoma.html b/concepts/duodenal-adenocarcinoma.html index 2af5aa3496..3667690b03 100644 --- a/concepts/duodenal-adenocarcinoma.html +++ b/concepts/duodenal-adenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/duodenal-type-follicular-lymphoma.html b/concepts/duodenal-type-follicular-lymphoma.html index c6d33bbfbd..e990d7c3c7 100644 --- a/concepts/duodenal-type-follicular-lymphoma.html +++ b/concepts/duodenal-type-follicular-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/dutch-cancer-society.html b/concepts/dutch-cancer-society.html index 8fadd8217f..f9080ff605 100644 --- a/concepts/dutch-cancer-society.html +++ b/concepts/dutch-cancer-society.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/dysembryoplastic-neuroepithelial-tumor.html b/concepts/dysembryoplastic-neuroepithelial-tumor.html index 99670d9b42..72a84b3105 100644 --- a/concepts/dysembryoplastic-neuroepithelial-tumor.html +++ b/concepts/dysembryoplastic-neuroepithelial-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/dysgerminoma-vulva-vagina.html b/concepts/dysgerminoma-vulva-vagina.html index 4f4c4b292d..d9a8eede76 100644 --- a/concepts/dysgerminoma-vulva-vagina.html +++ b/concepts/dysgerminoma-vulva-vagina.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/dysgerminoma.html b/concepts/dysgerminoma.html index 8b1e9da759..629d8c8892 100644 --- a/concepts/dysgerminoma.html +++ b/concepts/dysgerminoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/dysplastic-gangliocytoma-of-the-cerebellum-lhermitte-duclos-disease.html b/concepts/dysplastic-gangliocytoma-of-the-cerebellum-lhermitte-duclos-disease.html index b5a338ab61..6d883608da 100644 --- a/concepts/dysplastic-gangliocytoma-of-the-cerebellum-lhermitte-duclos-disease.html +++ b/concepts/dysplastic-gangliocytoma-of-the-cerebellum-lhermitte-duclos-disease.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/early-detection-book.html b/concepts/early-detection-book.html index 2877cdb058..02f62d5518 100644 --- a/concepts/early-detection-book.html +++ b/concepts/early-detection-book.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/early-t-cell-precursor-lymphoblastic-leukemia.html b/concepts/early-t-cell-precursor-lymphoblastic-leukemia.html index ee4084f961..237bf2e9c5 100644 --- a/concepts/early-t-cell-precursor-lymphoblastic-leukemia.html +++ b/concepts/early-t-cell-precursor-lymphoblastic-leukemia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ebus.html b/concepts/ebus.html index 7658e4eb7c..10ac7609a2 100644 --- a/concepts/ebus.html +++ b/concepts/ebus.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ebv-positive-dlbcl-nos.html b/concepts/ebv-positive-dlbcl-nos.html index f7300de4ac..9db946a628 100644 --- a/concepts/ebv-positive-dlbcl-nos.html +++ b/concepts/ebv-positive-dlbcl-nos.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ebv-positive-mucocutaneous-ulcer.html b/concepts/ebv-positive-mucocutaneous-ulcer.html index fd6caf850b..d430ff700c 100644 --- a/concepts/ebv-positive-mucocutaneous-ulcer.html +++ b/concepts/ebv-positive-mucocutaneous-ulcer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/egypt-ncc.html b/concepts/egypt-ncc.html index 5a23848096..a75e6fc599 100644 --- a/concepts/egypt-ncc.html +++ b/concepts/egypt-ncc.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/electroacupuncture.html b/concepts/electroacupuncture.html index 358210f9a0..4e0a8037f3 100644 --- a/concepts/electroacupuncture.html +++ b/concepts/electroacupuncture.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/eli-lilly-and-company.html b/concepts/eli-lilly-and-company.html index 7cefee4131..fc948fc675 100644 --- a/concepts/eli-lilly-and-company.html +++ b/concepts/eli-lilly-and-company.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/elsevier.html b/concepts/elsevier.html index 0bebd781d1..7f2417ddde 100644 --- a/concepts/elsevier.html +++ b/concepts/elsevier.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/embase.html b/concepts/embase.html index 2e1b576b4d..e6b5126478 100644 --- a/concepts/embase.html +++ b/concepts/embase.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/embryonal-carcinoma-cns-brain.html b/concepts/embryonal-carcinoma-cns-brain.html index 1d9adc731a..67aec0314f 100644 --- a/concepts/embryonal-carcinoma-cns-brain.html +++ b/concepts/embryonal-carcinoma-cns-brain.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/embryonal-carcinoma-testis.html b/concepts/embryonal-carcinoma-testis.html index af5c1a9892..c4806fa33c 100644 --- a/concepts/embryonal-carcinoma-testis.html +++ b/concepts/embryonal-carcinoma-testis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/embryonal-carcinoma-vulva-vagina.html b/concepts/embryonal-carcinoma-vulva-vagina.html index 1bf5b9b051..07a5214c19 100644 --- a/concepts/embryonal-carcinoma-vulva-vagina.html +++ b/concepts/embryonal-carcinoma-vulva-vagina.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/embryonal-carcinoma.html b/concepts/embryonal-carcinoma.html index ffe158304e..d77604e53f 100644 --- a/concepts/embryonal-carcinoma.html +++ b/concepts/embryonal-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/embryonal-rhabdomyosarcoma.html b/concepts/embryonal-rhabdomyosarcoma.html index 88d5307654..f6844aefd8 100644 --- a/concepts/embryonal-rhabdomyosarcoma.html +++ b/concepts/embryonal-rhabdomyosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/embryonal-tumor-with-abundant-neuropil-and-true-rosettes.html b/concepts/embryonal-tumor-with-abundant-neuropil-and-true-rosettes.html index 83101ff042..2ab00a7c4d 100644 --- a/concepts/embryonal-tumor-with-abundant-neuropil-and-true-rosettes.html +++ b/concepts/embryonal-tumor-with-abundant-neuropil-and-true-rosettes.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/embryonal-tumor.html b/concepts/embryonal-tumor.html index b26a79be0d..d7d584b6a9 100644 --- a/concepts/embryonal-tumor.html +++ b/concepts/embryonal-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/emerging-med.html b/concepts/emerging-med.html index 4ce0ae46f6..aca6fd3310 100644 --- a/concepts/emerging-med.html +++ b/concepts/emerging-med.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/emperor-of-all-maladies.html b/concepts/emperor-of-all-maladies.html index f7eeeb82a7..7ac7f62df8 100644 --- a/concepts/emperor-of-all-maladies.html +++ b/concepts/emperor-of-all-maladies.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/encapsulated-glioma.html b/concepts/encapsulated-glioma.html index a8174f6d5a..77853f286d 100644 --- a/concepts/encapsulated-glioma.html +++ b/concepts/encapsulated-glioma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/encr-sw.html b/concepts/encr-sw.html index 1137fcb367..1d31c679a2 100644 --- a/concepts/encr-sw.html +++ b/concepts/encr-sw.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/endocervical-adenocarcinoma.html b/concepts/endocervical-adenocarcinoma.html index 935cea7e68..551e68e85f 100644 --- a/concepts/endocervical-adenocarcinoma.html +++ b/concepts/endocervical-adenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/endocrine-mucin-producing-sweat-gland-carcinoma.html b/concepts/endocrine-mucin-producing-sweat-gland-carcinoma.html index 50360d39c9..901edb02fe 100644 --- a/concepts/endocrine-mucin-producing-sweat-gland-carcinoma.html +++ b/concepts/endocrine-mucin-producing-sweat-gland-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/endometrial-carcinoma.html b/concepts/endometrial-carcinoma.html index c258271cfb..1d2a5bb45e 100644 --- a/concepts/endometrial-carcinoma.html +++ b/concepts/endometrial-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/endometrial-stromal-sarcoma.html b/concepts/endometrial-stromal-sarcoma.html index afc4fa0fac..fab094e415 100644 --- a/concepts/endometrial-stromal-sarcoma.html +++ b/concepts/endometrial-stromal-sarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/endometrioid-borderlin-ovarian-tumor.html b/concepts/endometrioid-borderlin-ovarian-tumor.html index 02f16cf194..985e32f2b4 100644 --- a/concepts/endometrioid-borderlin-ovarian-tumor.html +++ b/concepts/endometrioid-borderlin-ovarian-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/endometrioid-ovarian-cancer.html b/concepts/endometrioid-ovarian-cancer.html index 10f4b0bc69..8e019f3535 100644 --- a/concepts/endometrioid-ovarian-cancer.html +++ b/concepts/endometrioid-ovarian-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/enteropathy-associated-t-cell-lymphoma.html b/concepts/enteropathy-associated-t-cell-lymphoma.html index b5c564f73f..a4d3408691 100644 --- a/concepts/enteropathy-associated-t-cell-lymphoma.html +++ b/concepts/enteropathy-associated-t-cell-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ependymoma.html b/concepts/ependymoma.html index 79eece7d9d..b7a6f5d00b 100644 --- a/concepts/ependymoma.html +++ b/concepts/ependymoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ependymomal-tumor.html b/concepts/ependymomal-tumor.html index d3a3b03c96..047c2d2515 100644 --- a/concepts/ependymomal-tumor.html +++ b/concepts/ependymomal-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/epirubicin.html b/concepts/epirubicin.html index 03370185ef..05c540799f 100644 --- a/concepts/epirubicin.html +++ b/concepts/epirubicin.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/epithelial-myoepithelial-carcinoma.html b/concepts/epithelial-myoepithelial-carcinoma.html index e97298e4a4..5c5de29b1e 100644 --- a/concepts/epithelial-myoepithelial-carcinoma.html +++ b/concepts/epithelial-myoepithelial-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/epithelial-type-metaplastic-breast-cancer.html b/concepts/epithelial-type-metaplastic-breast-cancer.html index 1d23e18701..1a8408e3bf 100644 --- a/concepts/epithelial-type-metaplastic-breast-cancer.html +++ b/concepts/epithelial-type-metaplastic-breast-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/epithelioid-hemangioendothelioma.html b/concepts/epithelioid-hemangioendothelioma.html index 30864dfff5..3132098b44 100644 --- a/concepts/epithelioid-hemangioendothelioma.html +++ b/concepts/epithelioid-hemangioendothelioma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/epithelioid-sarcoma.html b/concepts/epithelioid-sarcoma.html index 0ec08df338..e9d06a4580 100644 --- a/concepts/epithelioid-sarcoma.html +++ b/concepts/epithelioid-sarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/epithelioid-trophoblastic-tumor.html b/concepts/epithelioid-trophoblastic-tumor.html index 38397a9164..c48f2e406c 100644 --- a/concepts/epithelioid-trophoblastic-tumor.html +++ b/concepts/epithelioid-trophoblastic-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ercr-ar.html b/concepts/ercr-ar.html index e801770eff..6fbc8018f7 100644 --- a/concepts/ercr-ar.html +++ b/concepts/ercr-ar.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/erdheim-chester-disease.html b/concepts/erdheim-chester-disease.html index 7eafae9da1..d8aaa79073 100644 --- a/concepts/erdheim-chester-disease.html +++ b/concepts/erdheim-chester-disease.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/erlotinib.html b/concepts/erlotinib.html index b2c573a21b..a57cdb2e7a 100644 --- a/concepts/erlotinib.html +++ b/concepts/erlotinib.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/esophageal-adenocarcinoma.html b/concepts/esophageal-adenocarcinoma.html index 156407ef8e..5e6eea92a9 100644 --- a/concepts/esophageal-adenocarcinoma.html +++ b/concepts/esophageal-adenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/esophageal-poorly-differentiated-carcinoma.html b/concepts/esophageal-poorly-differentiated-carcinoma.html index f03ecee370..6edb4cad3f 100644 --- a/concepts/esophageal-poorly-differentiated-carcinoma.html +++ b/concepts/esophageal-poorly-differentiated-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/esophageal-squamous-cell-carcinoma.html b/concepts/esophageal-squamous-cell-carcinoma.html index 2cd8e873b1..4779765c37 100644 --- a/concepts/esophageal-squamous-cell-carcinoma.html +++ b/concepts/esophageal-squamous-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/esophagectomy.html b/concepts/esophagectomy.html index 91d277ca24..734ab394e6 100644 --- a/concepts/esophagectomy.html +++ b/concepts/esophagectomy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/esophagogastric-adenocarcinoma.html b/concepts/esophagogastric-adenocarcinoma.html index c6699c77a5..8aa2bca34a 100644 --- a/concepts/esophagogastric-adenocarcinoma.html +++ b/concepts/esophagogastric-adenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/esophagus-stomach.html b/concepts/esophagus-stomach.html index f0cc710f3b..d486168d68 100644 --- a/concepts/esophagus-stomach.html +++ b/concepts/esophagus-stomach.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/essential-thrombocythemia-myelofibrosis.html b/concepts/essential-thrombocythemia-myelofibrosis.html index b4a1b96d4f..fe6c5eae5f 100644 --- a/concepts/essential-thrombocythemia-myelofibrosis.html +++ b/concepts/essential-thrombocythemia-myelofibrosis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/essential-thrombocythemia.html b/concepts/essential-thrombocythemia.html index 677f945b10..0086f62ae3 100644 --- a/concepts/essential-thrombocythemia.html +++ b/concepts/essential-thrombocythemia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/etoposide.html b/concepts/etoposide.html index 4bc45dea0d..29e300d398 100644 --- a/concepts/etoposide.html +++ b/concepts/etoposide.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/europa-donna-armenia.html b/concepts/europa-donna-armenia.html index b3de2e8b14..ba26493d0c 100644 --- a/concepts/europa-donna-armenia.html +++ b/concepts/europa-donna-armenia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ewing-sarcoma-of-soft-tissue.html b/concepts/ewing-sarcoma-of-soft-tissue.html index 74f5997fba..697d5ebc10 100644 --- a/concepts/ewing-sarcoma-of-soft-tissue.html +++ b/concepts/ewing-sarcoma-of-soft-tissue.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ewing-sarcoma.html b/concepts/ewing-sarcoma.html index 9daa381128..d85795554f 100644 --- a/concepts/ewing-sarcoma.html +++ b/concepts/ewing-sarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/exatecan.html b/concepts/exatecan.html index 0cff4ed255..e0d54c6c3c 100644 --- a/concepts/exatecan.html +++ b/concepts/exatecan.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/exercise.html b/concepts/exercise.html index b21623dc0e..d6762afc29 100644 --- a/concepts/exercise.html +++ b/concepts/exercise.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/extra-gonadal-germ-cell-tumor.html b/concepts/extra-gonadal-germ-cell-tumor.html index 7fc08e0dd4..9bfeb59179 100644 --- a/concepts/extra-gonadal-germ-cell-tumor.html +++ b/concepts/extra-gonadal-germ-cell-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/extrahepatic-cholangiocarcinoma.html b/concepts/extrahepatic-cholangiocarcinoma.html index b456e2b86a..9980562a51 100644 --- a/concepts/extrahepatic-cholangiocarcinoma.html +++ b/concepts/extrahepatic-cholangiocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/extramammary-paget-disease.html b/concepts/extramammary-paget-disease.html index 3a3c0cd9e8..b81cb2ebd8 100644 --- a/concepts/extramammary-paget-disease.html +++ b/concepts/extramammary-paget-disease.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/extranodal-marginal-zone-lymphoma-of-mucosa-associated-lymphoid-tissue-malt-lymphoma.html b/concepts/extranodal-marginal-zone-lymphoma-of-mucosa-associated-lymphoid-tissue-malt-lymphoma.html index 85986251d2..d4aa67c135 100644 --- a/concepts/extranodal-marginal-zone-lymphoma-of-mucosa-associated-lymphoid-tissue-malt-lymphoma.html +++ b/concepts/extranodal-marginal-zone-lymphoma-of-mucosa-associated-lymphoid-tissue-malt-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/extranodal-nk--t-cell-lymphoma-nasal-type.html b/concepts/extranodal-nk--t-cell-lymphoma-nasal-type.html index 4172a42752..911c931be1 100644 --- a/concepts/extranodal-nk--t-cell-lymphoma-nasal-type.html +++ b/concepts/extranodal-nk--t-cell-lymphoma-nasal-type.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/extraosseous-plasmacytoma.html b/concepts/extraosseous-plasmacytoma.html index aa99e1f937..4d400b52d8 100644 --- a/concepts/extraosseous-plasmacytoma.html +++ b/concepts/extraosseous-plasmacytoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/extraskeletal-myxoid-chondrosarcoma.html b/concepts/extraskeletal-myxoid-chondrosarcoma.html index 29e5b28830..1bb52f7c05 100644 --- a/concepts/extraskeletal-myxoid-chondrosarcoma.html +++ b/concepts/extraskeletal-myxoid-chondrosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/extraventricular-neurocytoma.html b/concepts/extraventricular-neurocytoma.html index bdef28d2b4..85eaa43d40 100644 --- a/concepts/extraventricular-neurocytoma.html +++ b/concepts/extraventricular-neurocytoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/eye.html b/concepts/eye.html index 3a2b7747d1..8177450b47 100644 --- a/concepts/eye.html +++ b/concepts/eye.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/fda.html b/concepts/fda.html index 7c97987d95..14a9256566 100644 --- a/concepts/fda.html +++ b/concepts/fda.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/fecal-immunochemical-test.html b/concepts/fecal-immunochemical-test.html index 29bd68187d..4716a048e0 100644 --- a/concepts/fecal-immunochemical-test.html +++ b/concepts/fecal-immunochemical-test.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/fh-deficient-renal-cell-carcinoma.html b/concepts/fh-deficient-renal-cell-carcinoma.html index 02a53fd8f4..ea37778d1c 100644 --- a/concepts/fh-deficient-renal-cell-carcinoma.html +++ b/concepts/fh-deficient-renal-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/fibroadenoma.html b/concepts/fibroadenoma.html index a46a30cd87..636b48d06c 100644 --- a/concepts/fibroadenoma.html +++ b/concepts/fibroadenoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/fibroblastic-osteosarcoma.html b/concepts/fibroblastic-osteosarcoma.html index 32f6119124..f91703aeee 100644 --- a/concepts/fibroblastic-osteosarcoma.html +++ b/concepts/fibroblastic-osteosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/fibroblastic-reticular-cell-tumor.html b/concepts/fibroblastic-reticular-cell-tumor.html index e0c7d0535d..f28efd8e92 100644 --- a/concepts/fibroblastic-reticular-cell-tumor.html +++ b/concepts/fibroblastic-reticular-cell-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/fibrolamellar-carcinoma.html b/concepts/fibrolamellar-carcinoma.html index 97eb610dbb..04660224ce 100644 --- a/concepts/fibrolamellar-carcinoma.html +++ b/concepts/fibrolamellar-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/fibrosarcoma.html b/concepts/fibrosarcoma.html index 2e1958e983..26d43a9f4a 100644 --- a/concepts/fibrosarcoma.html +++ b/concepts/fibrosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/fibrothecoma.html b/concepts/fibrothecoma.html index 5f578baaf3..6fddc0f00d 100644 --- a/concepts/fibrothecoma.html +++ b/concepts/fibrothecoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/florid-follicular-hyperplasia-ptld.html b/concepts/florid-follicular-hyperplasia-ptld.html index 22e862cc02..e2184af547 100644 --- a/concepts/florid-follicular-hyperplasia-ptld.html +++ b/concepts/florid-follicular-hyperplasia-ptld.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/fluorouracil.html b/concepts/fluorouracil.html index a180e6b106..3a802c9f1d 100644 --- a/concepts/fluorouracil.html +++ b/concepts/fluorouracil.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/follicular-dendritic-cell-sarcoma.html b/concepts/follicular-dendritic-cell-sarcoma.html index 4b1903b247..763aa63c83 100644 --- a/concepts/follicular-dendritic-cell-sarcoma.html +++ b/concepts/follicular-dendritic-cell-sarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/follicular-lymphoma.html b/concepts/follicular-lymphoma.html index 47f750a799..79e5fb0728 100644 --- a/concepts/follicular-lymphoma.html +++ b/concepts/follicular-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/follicular-t-cell-lymphoma.html b/concepts/follicular-t-cell-lymphoma.html index 924ae3f420..c5f60d7c07 100644 --- a/concepts/follicular-t-cell-lymphoma.html +++ b/concepts/follicular-t-cell-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/follicular-thyroid-cancer.html b/concepts/follicular-thyroid-cancer.html index 21824c0acd..2d9891bcbc 100644 --- a/concepts/follicular-thyroid-cancer.html +++ b/concepts/follicular-thyroid-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/fotemustine.html b/concepts/fotemustine.html index 83e813797b..e105c27a4d 100644 --- a/concepts/fotemustine.html +++ b/concepts/fotemustine.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/fox-chase-cancer-center.html b/concepts/fox-chase-cancer-center.html index 6aa588001b..c384d6a953 100644 --- a/concepts/fox-chase-cancer-center.html +++ b/concepts/fox-chase-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/fred-and-pamela-buffett-cancer-center.html b/concepts/fred-and-pamela-buffett-cancer-center.html index 68853c7d0f..097aea28f8 100644 --- a/concepts/fred-and-pamela-buffett-cancer-center.html +++ b/concepts/fred-and-pamela-buffett-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/fred-hutch.html b/concepts/fred-hutch.html index f56b7d3c2b..d8f839702e 100644 --- a/concepts/fred-hutch.html +++ b/concepts/fred-hutch.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/fred-hutchinson-university-of-washington-cancer-consortium.html b/concepts/fred-hutchinson-university-of-washington-cancer-consortium.html index 47c3730810..5d0097f521 100644 --- a/concepts/fred-hutchinson-university-of-washington-cancer-consortium.html +++ b/concepts/fred-hutchinson-university-of-washington-cancer-consortium.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/free-diving.html b/concepts/free-diving.html index ca864a1118..fadfe48cf5 100644 --- a/concepts/free-diving.html +++ b/concepts/free-diving.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/french-national-cancer-institute.html b/concepts/french-national-cancer-institute.html index 68337b1b1d..0fea2d9712 100644 --- a/concepts/french-national-cancer-institute.html +++ b/concepts/french-national-cancer-institute.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/gallbladder-adenocarcinoma-nos.html b/concepts/gallbladder-adenocarcinoma-nos.html index 3fa73ae282..afc019052c 100644 --- a/concepts/gallbladder-adenocarcinoma-nos.html +++ b/concepts/gallbladder-adenocarcinoma-nos.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/gallbladder-cancer.html b/concepts/gallbladder-cancer.html index ad5c7788fe..c5583ce22a 100644 --- a/concepts/gallbladder-cancer.html +++ b/concepts/gallbladder-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/gamma-heavy-chain-disease.html b/concepts/gamma-heavy-chain-disease.html index bf76550a71..f706031f75 100644 --- a/concepts/gamma-heavy-chain-disease.html +++ b/concepts/gamma-heavy-chain-disease.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/gangliocytoma.html b/concepts/gangliocytoma.html index a627a4ffdd..6bd0f57745 100644 --- a/concepts/gangliocytoma.html +++ b/concepts/gangliocytoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ganglioglioma.html b/concepts/ganglioglioma.html index b99beb1de9..8dff931d69 100644 --- a/concepts/ganglioglioma.html +++ b/concepts/ganglioglioma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ganglioneuroblastoma.html b/concepts/ganglioneuroblastoma.html index 1bdd5bb268..ea671f0af8 100644 --- a/concepts/ganglioneuroblastoma.html +++ b/concepts/ganglioneuroblastoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ganglioneuroma.html b/concepts/ganglioneuroma.html index f74d2600a8..a527eec07e 100644 --- a/concepts/ganglioneuroma.html +++ b/concepts/ganglioneuroma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/gastrectomy.html b/concepts/gastrectomy.html index f54d486868..7e70e14906 100644 --- a/concepts/gastrectomy.html +++ b/concepts/gastrectomy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/gastric-remnant-adenocarcinoma.html b/concepts/gastric-remnant-adenocarcinoma.html index b4d70d7207..501721251d 100644 --- a/concepts/gastric-remnant-adenocarcinoma.html +++ b/concepts/gastric-remnant-adenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/gastric-type-mucinous-carcinoma.html b/concepts/gastric-type-mucinous-carcinoma.html index 08a635a5c8..695875ef43 100644 --- a/concepts/gastric-type-mucinous-carcinoma.html +++ b/concepts/gastric-type-mucinous-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/gastrointestinal-neuroendocrine-tumors-of-the-esophagus-stomach.html b/concepts/gastrointestinal-neuroendocrine-tumors-of-the-esophagus-stomach.html index fded5ba442..6efd2517bd 100644 --- a/concepts/gastrointestinal-neuroendocrine-tumors-of-the-esophagus-stomach.html +++ b/concepts/gastrointestinal-neuroendocrine-tumors-of-the-esophagus-stomach.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/gastrointestinal-neuroendocrine-tumors.html b/concepts/gastrointestinal-neuroendocrine-tumors.html index ebe3697272..0028415657 100644 --- a/concepts/gastrointestinal-neuroendocrine-tumors.html +++ b/concepts/gastrointestinal-neuroendocrine-tumors.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/gastrointestinal-stromal-tumor.html b/concepts/gastrointestinal-stromal-tumor.html index f1916f6d94..27e0de1e48 100644 --- a/concepts/gastrointestinal-stromal-tumor.html +++ b/concepts/gastrointestinal-stromal-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/gco.html b/concepts/gco.html index e6330d9928..f627c07c86 100644 --- a/concepts/gco.html +++ b/concepts/gco.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/gcr-ug.html b/concepts/gcr-ug.html index b597eafc72..9eeb613e19 100644 --- a/concepts/gcr-ug.html +++ b/concepts/gcr-ug.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/gdc.html b/concepts/gdc.html index a2f18d0d9c..78ebad9841 100644 --- a/concepts/gdc.html +++ b/concepts/gdc.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/gefitinib.html b/concepts/gefitinib.html index 9e697e1b7a..f2003a297e 100644 --- a/concepts/gefitinib.html +++ b/concepts/gefitinib.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/gemcitabine.html b/concepts/gemcitabine.html index 6623bea4fa..65502847f0 100644 --- a/concepts/gemcitabine.html +++ b/concepts/gemcitabine.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/genentech.html b/concepts/genentech.html index 9db07a6048..94c765cc9f 100644 --- a/concepts/genentech.html +++ b/concepts/genentech.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/georgetown-lombardi-comprehensive-cancer-center.html b/concepts/georgetown-lombardi-comprehensive-cancer-center.html index 6c676885bc..d85eff2a45 100644 --- a/concepts/georgetown-lombardi-comprehensive-cancer-center.html +++ b/concepts/georgetown-lombardi-comprehensive-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/georgia-ncc.html b/concepts/georgia-ncc.html index c8affe7d9b..296fb8e291 100644 --- a/concepts/georgia-ncc.html +++ b/concepts/georgia-ncc.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/germ-cell-tumor-brain.html b/concepts/germ-cell-tumor-brain.html index ecb73ce1cd..dea154f27a 100644 --- a/concepts/germ-cell-tumor-brain.html +++ b/concepts/germ-cell-tumor-brain.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/germ-cell-tumor-of-the-vulva.html b/concepts/germ-cell-tumor-of-the-vulva.html index 92aaed3e2c..050364da2a 100644 --- a/concepts/germ-cell-tumor-of-the-vulva.html +++ b/concepts/germ-cell-tumor-of-the-vulva.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/germ-cell-tumor-with-somatic-type-malignancy.html b/concepts/germ-cell-tumor-with-somatic-type-malignancy.html index de4027dc07..74d2cc3a5f 100644 --- a/concepts/germ-cell-tumor-with-somatic-type-malignancy.html +++ b/concepts/germ-cell-tumor-with-somatic-type-malignancy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/germany-ncc.html b/concepts/germany-ncc.html index 10606b858d..8b68b20b87 100644 --- a/concepts/germany-ncc.html +++ b/concepts/germany-ncc.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/germinal-center-b-cell-type.html b/concepts/germinal-center-b-cell-type.html index 5b02d26f20..1a9cb84c87 100644 --- a/concepts/germinal-center-b-cell-type.html +++ b/concepts/germinal-center-b-cell-type.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/germinoma.html b/concepts/germinoma.html index 10e9c8803f..19d5d288e6 100644 --- a/concepts/germinoma.html +++ b/concepts/germinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/gestational-trophoblastic-disease.html b/concepts/gestational-trophoblastic-disease.html index a3ff2e8340..802493083b 100644 --- a/concepts/gestational-trophoblastic-disease.html +++ b/concepts/gestational-trophoblastic-disease.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/gfobt.html b/concepts/gfobt.html index f2c68ceeb5..362b1a6c0d 100644 --- a/concepts/gfobt.html +++ b/concepts/gfobt.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/giant-cell-carcinoma-of-the-lung.html b/concepts/giant-cell-carcinoma-of-the-lung.html index 830a8709a5..b10710ddb1 100644 --- a/concepts/giant-cell-carcinoma-of-the-lung.html +++ b/concepts/giant-cell-carcinoma-of-the-lung.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/giant-cell-tumor-of-bone.html b/concepts/giant-cell-tumor-of-bone.html index ed1477717f..88b9130603 100644 --- a/concepts/giant-cell-tumor-of-bone.html +++ b/concepts/giant-cell-tumor-of-bone.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/gideon-burrows-dot-com.html b/concepts/gideon-burrows-dot-com.html index ffc6bdc0b9..d16cfc1fa4 100644 --- a/concepts/gideon-burrows-dot-com.html +++ b/concepts/gideon-burrows-dot-com.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/gilead-sciences.html b/concepts/gilead-sciences.html index 5288245775..e5c11363a6 100644 --- a/concepts/gilead-sciences.html +++ b/concepts/gilead-sciences.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/glassy-cell-carcinoma-of-the-cervix.html b/concepts/glassy-cell-carcinoma-of-the-cervix.html index b5f6821d75..f9f9c68780 100644 --- a/concepts/glassy-cell-carcinoma-of-the-cervix.html +++ b/concepts/glassy-cell-carcinoma-of-the-cervix.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/glioblastoma-multiforme.html b/concepts/glioblastoma-multiforme.html index d7e259abcd..5b9bfa045e 100644 --- a/concepts/glioblastoma-multiforme.html +++ b/concepts/glioblastoma-multiforme.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/glioblastoma.html b/concepts/glioblastoma.html index d67a9de1ae..92e4dfe554 100644 --- a/concepts/glioblastoma.html +++ b/concepts/glioblastoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/glioma-nos.html b/concepts/glioma-nos.html index cce1e1ca15..be0b0582dc 100644 --- a/concepts/glioma-nos.html +++ b/concepts/glioma-nos.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/gliosarcoma.html b/concepts/gliosarcoma.html index bae2072d78..6acc95e100 100644 --- a/concepts/gliosarcoma.html +++ b/concepts/gliosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/glomangiosarcoma.html b/concepts/glomangiosarcoma.html index f2b8ec8887..c999f49881 100644 --- a/concepts/glomangiosarcoma.html +++ b/concepts/glomangiosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/goblet-cell-carcinoid-of-the-appendix.html b/concepts/goblet-cell-carcinoid-of-the-appendix.html index 3719eddee5..618876d830 100644 --- a/concepts/goblet-cell-carcinoid-of-the-appendix.html +++ b/concepts/goblet-cell-carcinoid-of-the-appendix.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/gonadoblastoma.html b/concepts/gonadoblastoma.html index df4f7784e0..e7a007a164 100644 --- a/concepts/gonadoblastoma.html +++ b/concepts/gonadoblastoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/granular-cell-tumor.html b/concepts/granular-cell-tumor.html index c42a0dfa20..b49d44df88 100644 --- a/concepts/granular-cell-tumor.html +++ b/concepts/granular-cell-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/granulosa-cell-tumor.html b/concepts/granulosa-cell-tumor.html index b1bc81bc8a..3b6dd8911d 100644 --- a/concepts/granulosa-cell-tumor.html +++ b/concepts/granulosa-cell-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/gsk.html b/concepts/gsk.html index 6ae80437cb..130cf04d24 100644 --- a/concepts/gsk.html +++ b/concepts/gsk.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/hairy-cell-leukemia-variant.html b/concepts/hairy-cell-leukemia-variant.html index de1603358c..4cd71cfe2c 100644 --- a/concepts/hairy-cell-leukemia-variant.html +++ b/concepts/hairy-cell-leukemia-variant.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/hairy-cell-leukemia.html b/concepts/hairy-cell-leukemia.html index 788273d1cf..eea013da15 100644 --- a/concepts/hairy-cell-leukemia.html +++ b/concepts/hairy-cell-leukemia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/harold-c-simmons-comprehensive-cancer-center.html b/concepts/harold-c-simmons-comprehensive-cancer-center.html index a08b5a9761..9f08791ab8 100644 --- a/concepts/harold-c-simmons-comprehensive-cancer-center.html +++ b/concepts/harold-c-simmons-comprehensive-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/hawaii-tumor-registry.html b/concepts/hawaii-tumor-registry.html index dcc271578c..5646a348ef 100644 --- a/concepts/hawaii-tumor-registry.html +++ b/concepts/hawaii-tumor-registry.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/head-and-neck-carcinoma-other.html b/concepts/head-and-neck-carcinoma-other.html index 3573e1b00f..4e319ef779 100644 --- a/concepts/head-and-neck-carcinoma-other.html +++ b/concepts/head-and-neck-carcinoma-other.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/head-and-neck-mucosal-melanoma.html b/concepts/head-and-neck-mucosal-melanoma.html index 7e80eb5a78..6b13bd0a8b 100644 --- a/concepts/head-and-neck-mucosal-melanoma.html +++ b/concepts/head-and-neck-mucosal-melanoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/head-and-neck-neuroendocrine-carcinoma.html b/concepts/head-and-neck-neuroendocrine-carcinoma.html index c55d6ba6ae..0284e908db 100644 --- a/concepts/head-and-neck-neuroendocrine-carcinoma.html +++ b/concepts/head-and-neck-neuroendocrine-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/head-and-neck-squamous-cell-carcinoma-of-unknown-primary.html b/concepts/head-and-neck-squamous-cell-carcinoma-of-unknown-primary.html index e018b8e533..21d75d39a5 100644 --- a/concepts/head-and-neck-squamous-cell-carcinoma-of-unknown-primary.html +++ b/concepts/head-and-neck-squamous-cell-carcinoma-of-unknown-primary.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/head-and-neck-squamous-cell-carcinoma.html b/concepts/head-and-neck-squamous-cell-carcinoma.html index be868d0735..432c41590c 100644 --- a/concepts/head-and-neck-squamous-cell-carcinoma.html +++ b/concepts/head-and-neck-squamous-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/head-and-neck.html b/concepts/head-and-neck.html index 2e51817a3e..a54b07c935 100644 --- a/concepts/head-and-neck.html +++ b/concepts/head-and-neck.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/healing-strong.html b/concepts/healing-strong.html index 1a08f65acb..7ab0ead38a 100644 --- a/concepts/healing-strong.html +++ b/concepts/healing-strong.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/hemangioblastoma.html b/concepts/hemangioblastoma.html index 6041c5fb6d..19d9a6cbb3 100644 --- a/concepts/hemangioblastoma.html +++ b/concepts/hemangioblastoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/hemangioma.html b/concepts/hemangioma.html index 7fd62af09b..9face3ba35 100644 --- a/concepts/hemangioma.html +++ b/concepts/hemangioma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/hemangiopericytoma-of-the-central-nervous-system.html b/concepts/hemangiopericytoma-of-the-central-nervous-system.html index 2996b94982..743b17d741 100644 --- a/concepts/hemangiopericytoma-of-the-central-nervous-system.html +++ b/concepts/hemangiopericytoma-of-the-central-nervous-system.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/hepatectomy.html b/concepts/hepatectomy.html index fce0276add..4f646b558b 100644 --- a/concepts/hepatectomy.html +++ b/concepts/hepatectomy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/hepatic-arterial-infusion.html b/concepts/hepatic-arterial-infusion.html index 93628791fc..5671549a7d 100644 --- a/concepts/hepatic-arterial-infusion.html +++ b/concepts/hepatic-arterial-infusion.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/hepatoblastoma.html b/concepts/hepatoblastoma.html index 0fc7685bca..b7e98a4d21 100644 --- a/concepts/hepatoblastoma.html +++ b/concepts/hepatoblastoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/hepatocellular-adenoma.html b/concepts/hepatocellular-adenoma.html index f08208acc3..643fb57ca4 100644 --- a/concepts/hepatocellular-adenoma.html +++ b/concepts/hepatocellular-adenoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/hepatocellular-carcinoma-plus-intrahepatic-cholangiocarcinoma.html b/concepts/hepatocellular-carcinoma-plus-intrahepatic-cholangiocarcinoma.html index 2f7c8fb717..4b17ec3618 100644 --- a/concepts/hepatocellular-carcinoma-plus-intrahepatic-cholangiocarcinoma.html +++ b/concepts/hepatocellular-carcinoma-plus-intrahepatic-cholangiocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/hepatocellular-carcinoma.html b/concepts/hepatocellular-carcinoma.html index 5ad2c6514b..7fd938dada 100644 --- a/concepts/hepatocellular-carcinoma.html +++ b/concepts/hepatocellular-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/hepatosplenic-t-cell-lymphoma.html b/concepts/hepatosplenic-t-cell-lymphoma.html index 21375f30fe..dfc5ed32b3 100644 --- a/concepts/hepatosplenic-t-cell-lymphoma.html +++ b/concepts/hepatosplenic-t-cell-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/herbert-irving-comprehensive-cancer-center.html b/concepts/herbert-irving-comprehensive-cancer-center.html index 64b5c1b3e5..9e2932d491 100644 --- a/concepts/herbert-irving-comprehensive-cancer-center.html +++ b/concepts/herbert-irving-comprehensive-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/hhs.html b/concepts/hhs.html index 69a36ea489..ab54ed2690 100644 --- a/concepts/hhs.html +++ b/concepts/hhs.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/hhv8-positive-dlbcl-nos.html b/concepts/hhv8-positive-dlbcl-nos.html index 879aa18c2d..bd471c4ad6 100644 --- a/concepts/hhv8-positive-dlbcl-nos.html +++ b/concepts/hhv8-positive-dlbcl-nos.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/high-grade-b-cell-lymphoma-nos.html b/concepts/high-grade-b-cell-lymphoma-nos.html index 214b26cc42..be1f484b1d 100644 --- a/concepts/high-grade-b-cell-lymphoma-nos.html +++ b/concepts/high-grade-b-cell-lymphoma-nos.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/high-grade-b-cell-lymphoma-with-myc-and-bcl2-and-or-bcl6-rearrangements.html b/concepts/high-grade-b-cell-lymphoma-with-myc-and-bcl2-and-or-bcl6-rearrangements.html index 45bdbc2c3b..78a51fb81c 100644 --- a/concepts/high-grade-b-cell-lymphoma-with-myc-and-bcl2-and-or-bcl6-rearrangements.html +++ b/concepts/high-grade-b-cell-lymphoma-with-myc-and-bcl2-and-or-bcl6-rearrangements.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/high-grade-endometrial-stromal-sarcoma.html b/concepts/high-grade-endometrial-stromal-sarcoma.html index 39fcfd42ee..d15da71818 100644 --- a/concepts/high-grade-endometrial-stromal-sarcoma.html +++ b/concepts/high-grade-endometrial-stromal-sarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/high-grade-glioma-nos.html b/concepts/high-grade-glioma-nos.html index d8ae0941ed..3f7954cbe7 100644 --- a/concepts/high-grade-glioma-nos.html +++ b/concepts/high-grade-glioma-nos.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/high-grade-neuroendocrine-carcinoma-of-the-colon-and-rectum.html b/concepts/high-grade-neuroendocrine-carcinoma-of-the-colon-and-rectum.html index 7eb8b68714..838308f5e3 100644 --- a/concepts/high-grade-neuroendocrine-carcinoma-of-the-colon-and-rectum.html +++ b/concepts/high-grade-neuroendocrine-carcinoma-of-the-colon-and-rectum.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/high-grade-neuroendocrine-carcinoma-of-the-esophagus.html b/concepts/high-grade-neuroendocrine-carcinoma-of-the-esophagus.html index cb010c8b57..95a5c493b6 100644 --- a/concepts/high-grade-neuroendocrine-carcinoma-of-the-esophagus.html +++ b/concepts/high-grade-neuroendocrine-carcinoma-of-the-esophagus.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/high-grade-neuroendocrine-carcinoma-of-the-ovary.html b/concepts/high-grade-neuroendocrine-carcinoma-of-the-ovary.html index 052ed848d1..58d58c7711 100644 --- a/concepts/high-grade-neuroendocrine-carcinoma-of-the-ovary.html +++ b/concepts/high-grade-neuroendocrine-carcinoma-of-the-ovary.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/high-grade-neuroendocrine-carcinoma-of-the-stomach.html b/concepts/high-grade-neuroendocrine-carcinoma-of-the-stomach.html index dff3f5e4c5..ab856e6c1b 100644 --- a/concepts/high-grade-neuroendocrine-carcinoma-of-the-stomach.html +++ b/concepts/high-grade-neuroendocrine-carcinoma-of-the-stomach.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/high-grade-neuroepithelial-tumor.html b/concepts/high-grade-neuroepithelial-tumor.html index dd1f231d36..ea3487bc45 100644 --- a/concepts/high-grade-neuroepithelial-tumor.html +++ b/concepts/high-grade-neuroepithelial-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/high-grade-serous-fallopian-tube-cancer.html b/concepts/high-grade-serous-fallopian-tube-cancer.html index 6a68bb3b7c..464caac7b8 100644 --- a/concepts/high-grade-serous-fallopian-tube-cancer.html +++ b/concepts/high-grade-serous-fallopian-tube-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/high-grade-serous-ovarian-cancer.html b/concepts/high-grade-serous-ovarian-cancer.html index b8dd216918..68316b34f1 100644 --- a/concepts/high-grade-serous-ovarian-cancer.html +++ b/concepts/high-grade-serous-ovarian-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/high-grade-surface-osteosarcoma.html b/concepts/high-grade-surface-osteosarcoma.html index 2b8f759db1..baf09859ca 100644 --- a/concepts/high-grade-surface-osteosarcoma.html +++ b/concepts/high-grade-surface-osteosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/histiocytic-and-dendritic-cell-neoplasms.html b/concepts/histiocytic-and-dendritic-cell-neoplasms.html index 88dfb4f18e..5706e8355f 100644 --- a/concepts/histiocytic-and-dendritic-cell-neoplasms.html +++ b/concepts/histiocytic-and-dendritic-cell-neoplasms.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/histiocytic-dendritic-cell-sarcoma.html b/concepts/histiocytic-dendritic-cell-sarcoma.html index 15f7ba20eb..44ee0f2063 100644 --- a/concepts/histiocytic-dendritic-cell-sarcoma.html +++ b/concepts/histiocytic-dendritic-cell-sarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/histiocytic-sarcoma.html b/concepts/histiocytic-sarcoma.html index 1fa03975ba..ae8cd43486 100644 --- a/concepts/histiocytic-sarcoma.html +++ b/concepts/histiocytic-sarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/hodgkin-lymphoma.html b/concepts/hodgkin-lymphoma.html index 3af67b2b53..6f59ff83e5 100644 --- a/concepts/hodgkin-lymphoma.html +++ b/concepts/hodgkin-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/holden-comprehensive-cancer-center.html b/concepts/holden-comprehensive-cancer-center.html index 59168a1968..487fd01208 100644 --- a/concepts/holden-comprehensive-cancer-center.html +++ b/concepts/holden-comprehensive-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/hollings-cancer-center.html b/concepts/hollings-cancer-center.html index 2662f4c198..d418909fd8 100644 --- a/concepts/hollings-cancer-center.html +++ b/concepts/hollings-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/hopa.html b/concepts/hopa.html index 0eee857f41..f3693131f6 100644 --- a/concepts/hopa.html +++ b/concepts/hopa.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/hope4cancer.html b/concepts/hope4cancer.html index 469e937e75..22d55f5ff4 100644 --- a/concepts/hope4cancer.html +++ b/concepts/hope4cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/houston-methodist.html b/concepts/houston-methodist.html index fb7b680597..7490a2129a 100644 --- a/concepts/houston-methodist.html +++ b/concepts/houston-methodist.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/how-to-starve-cancer-book.html b/concepts/how-to-starve-cancer-book.html index 42c74894c1..f5229f718a 100644 --- a/concepts/how-to-starve-cancer-book.html +++ b/concepts/how-to-starve-cancer-book.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/hungary-ncc.html b/concepts/hungary-ncc.html index d4e6139305..85a6058e59 100644 --- a/concepts/hungary-ncc.html +++ b/concepts/hungary-ncc.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/huntsman-cancer-institute.html b/concepts/huntsman-cancer-institute.html index f30c1301d6..e1f51ed7d7 100644 --- a/concepts/huntsman-cancer-institute.html +++ b/concepts/huntsman-cancer-institute.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/hurthle-cell-thyroid-cancer.html b/concepts/hurthle-cell-thyroid-cancer.html index 067d44be94..1ee91886cd 100644 --- a/concepts/hurthle-cell-thyroid-cancer.html +++ b/concepts/hurthle-cell-thyroid-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/hyalinizing-trabecular-adenoma-of-the-thyroid.html b/concepts/hyalinizing-trabecular-adenoma-of-the-thyroid.html index 268bf1f7b2..6d5bd120cd 100644 --- a/concepts/hyalinizing-trabecular-adenoma-of-the-thyroid.html +++ b/concepts/hyalinizing-trabecular-adenoma-of-the-thyroid.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/hydroa-vacciniforme-like-lymphoproliferative-disorder.html b/concepts/hydroa-vacciniforme-like-lymphoproliferative-disorder.html index 0f2c2a2666..76b4869b8d 100644 --- a/concepts/hydroa-vacciniforme-like-lymphoproliferative-disorder.html +++ b/concepts/hydroa-vacciniforme-like-lymphoproliferative-disorder.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/hydroxyurea.html b/concepts/hydroxyurea.html index 9141204426..29a7913146 100644 --- a/concepts/hydroxyurea.html +++ b/concepts/hydroxyurea.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/hypnosis.html b/concepts/hypnosis.html index eef9592907..c3e155aa68 100644 --- a/concepts/hypnosis.html +++ b/concepts/hypnosis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/hypopharynx-squamous-cell-carcinoma.html b/concepts/hypopharynx-squamous-cell-carcinoma.html index 33751945fa..4165cd2922 100644 --- a/concepts/hypopharynx-squamous-cell-carcinoma.html +++ b/concepts/hypopharynx-squamous-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/hysterectomy.html b/concepts/hysterectomy.html index 5cfa031425..e7561bf8b0 100644 --- a/concepts/hysterectomy.html +++ b/concepts/hysterectomy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/iacc-ao.html b/concepts/iacc-ao.html index d6f44a150c..23fdebc43c 100644 --- a/concepts/iacc-ao.html +++ b/concepts/iacc-ao.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/iaea.html b/concepts/iaea.html index c953471265..2364b6b3d2 100644 --- a/concepts/iaea.html +++ b/concepts/iaea.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/iarc.html b/concepts/iarc.html index 64fadb44be..43c97d6979 100644 --- a/concepts/iarc.html +++ b/concepts/iarc.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ibcr-ng.html b/concepts/ibcr-ng.html index c5bc317c8f..178a648e2e 100644 --- a/concepts/ibcr-ng.html +++ b/concepts/ibcr-ng.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ican.html b/concepts/ican.html index eb2d39b29a..10318db408 100644 --- a/concepts/ican.html +++ b/concepts/ican.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/icd-10-pcs.html b/concepts/icd-10-pcs.html index af2252103e..1d9698efe7 100644 --- a/concepts/icd-10-pcs.html +++ b/concepts/icd-10-pcs.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/icelandic-cancer-society.html b/concepts/icelandic-cancer-society.html index 9aadc0ddf1..e3e5ad8e82 100644 --- a/concepts/icelandic-cancer-society.html +++ b/concepts/icelandic-cancer-society.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/idarubicin.html b/concepts/idarubicin.html index 1bb5c0d701..b63071f024 100644 --- a/concepts/idarubicin.html +++ b/concepts/idarubicin.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ifosfamide.html b/concepts/ifosfamide.html index 06db23f730..98e7a442e9 100644 --- a/concepts/ifosfamide.html +++ b/concepts/ifosfamide.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/iga.html b/concepts/iga.html index 37f8a48343..88de9e5fd3 100644 --- a/concepts/iga.html +++ b/concepts/iga.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/igg.html b/concepts/igg.html index 22c1accf68..f6727b351c 100644 --- a/concepts/igg.html +++ b/concepts/igg.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/igm.html b/concepts/igm.html index cfdef547d4..8e3186917f 100644 --- a/concepts/igm.html +++ b/concepts/igm.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/imatinib.html b/concepts/imatinib.html index 94170789f9..bec2e5db87 100644 --- a/concepts/imatinib.html +++ b/concepts/imatinib.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/imerman-angels.html b/concepts/imerman-angels.html index de174a0759..15dc1e7bde 100644 --- a/concepts/imerman-angels.html +++ b/concepts/imerman-angels.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/immature-teratoma-cns-brain.html b/concepts/immature-teratoma-cns-brain.html index f669b52f0c..5216a26282 100644 --- a/concepts/immature-teratoma-cns-brain.html +++ b/concepts/immature-teratoma-cns-brain.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/immature-teratoma-vulva-vagina.html b/concepts/immature-teratoma-vulva-vagina.html index 523365a415..4b82071314 100644 --- a/concepts/immature-teratoma-vulva-vagina.html +++ b/concepts/immature-teratoma-vulva-vagina.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/immature-teratoma.html b/concepts/immature-teratoma.html index 5fda1df4af..b61751b9ab 100644 --- a/concepts/immature-teratoma.html +++ b/concepts/immature-teratoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/immunotherapy.html b/concepts/immunotherapy.html index 2e4e235aa8..d6e2c95955 100644 --- a/concepts/immunotherapy.html +++ b/concepts/immunotherapy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/imrt.html b/concepts/imrt.html index d9322b338b..fd703666ed 100644 --- a/concepts/imrt.html +++ b/concepts/imrt.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/in-situ-follicular-neoplasia.html b/concepts/in-situ-follicular-neoplasia.html index 32c714841e..362af08ab6 100644 --- a/concepts/in-situ-follicular-neoplasia.html +++ b/concepts/in-situ-follicular-neoplasia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/in-situ-mantle-cell-neoplasia.html b/concepts/in-situ-mantle-cell-neoplasia.html index b5cedf0ff4..4c93a07b0f 100644 --- a/concepts/in-situ-mantle-cell-neoplasia.html +++ b/concepts/in-situ-mantle-cell-neoplasia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/incan.html b/concepts/incan.html index 8a0abb2f8e..bceeb04351 100644 --- a/concepts/incan.html +++ b/concepts/incan.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/indeterminate-dendritic-cell-tumor.html b/concepts/indeterminate-dendritic-cell-tumor.html index 084756fb70..601ab203b1 100644 --- a/concepts/indeterminate-dendritic-cell-tumor.html +++ b/concepts/indeterminate-dendritic-cell-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/index-medicus.html b/concepts/index-medicus.html index 4d6c870c7a..f191130f4a 100644 --- a/concepts/index-medicus.html +++ b/concepts/index-medicus.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/india-nci.html b/concepts/india-nci.html index 57f502c971..bfbd9227d2 100644 --- a/concepts/india-nci.html +++ b/concepts/india-nci.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/indiana-university-melvin-and-bren-simon-comprehensive-cancer-center.html b/concepts/indiana-university-melvin-and-bren-simon-comprehensive-cancer-center.html index 0c0b864d8a..d0a1ec4ca8 100644 --- a/concepts/indiana-university-melvin-and-bren-simon-comprehensive-cancer-center.html +++ b/concepts/indiana-university-melvin-and-bren-simon-comprehensive-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/indolent-systemic-mastocytosis.html b/concepts/indolent-systemic-mastocytosis.html index 8cd19a5409..8ddbf24684 100644 --- a/concepts/indolent-systemic-mastocytosis.html +++ b/concepts/indolent-systemic-mastocytosis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/indolent-t-cell-lymphoproliferative-disorder-of-the-gi-tract.html b/concepts/indolent-t-cell-lymphoproliferative-disorder-of-the-gi-tract.html index 0834669baf..25b78047c7 100644 --- a/concepts/indolent-t-cell-lymphoproliferative-disorder-of-the-gi-tract.html +++ b/concepts/indolent-t-cell-lymphoproliferative-disorder-of-the-gi-tract.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/infantile-fibrosarcoma.html b/concepts/infantile-fibrosarcoma.html index 131d224d53..901dfd2326 100644 --- a/concepts/infantile-fibrosarcoma.html +++ b/concepts/infantile-fibrosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/infectious-mononucleosis-ptld.html b/concepts/infectious-mononucleosis-ptld.html index 4315393d29..5c2f63419d 100644 --- a/concepts/infectious-mononucleosis-ptld.html +++ b/concepts/infectious-mononucleosis-ptld.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/inflammatory-breast-cancer.html b/concepts/inflammatory-breast-cancer.html index c8368c03d0..1c9e9cfc59 100644 --- a/concepts/inflammatory-breast-cancer.html +++ b/concepts/inflammatory-breast-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/inflammatory-myofibroblastic-bladder-tumor.html b/concepts/inflammatory-myofibroblastic-bladder-tumor.html index e5c40ef314..2e437442f2 100644 --- a/concepts/inflammatory-myofibroblastic-bladder-tumor.html +++ b/concepts/inflammatory-myofibroblastic-bladder-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/inflammatory-myofibroblastic-lung-tumor.html b/concepts/inflammatory-myofibroblastic-lung-tumor.html index a80f510d30..aa469c83f5 100644 --- a/concepts/inflammatory-myofibroblastic-lung-tumor.html +++ b/concepts/inflammatory-myofibroblastic-lung-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/inflammatory-myofibroblastic-tumor.html b/concepts/inflammatory-myofibroblastic-tumor.html index 31b855ccc3..1d24d304c9 100644 --- a/concepts/inflammatory-myofibroblastic-tumor.html +++ b/concepts/inflammatory-myofibroblastic-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/interdigitating-dendritic-cell-sarcoma.html b/concepts/interdigitating-dendritic-cell-sarcoma.html index 60074454ad..a86f8f2e17 100644 --- a/concepts/interdigitating-dendritic-cell-sarcoma.html +++ b/concepts/interdigitating-dendritic-cell-sarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/intestinal-ampullary-carcinoma.html b/concepts/intestinal-ampullary-carcinoma.html index b0a088617c..2a8559e89c 100644 --- a/concepts/intestinal-ampullary-carcinoma.html +++ b/concepts/intestinal-ampullary-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/intestinal-type-mucinous-carcinoma.html b/concepts/intestinal-type-mucinous-carcinoma.html index 507c132af5..9b32aba590 100644 --- a/concepts/intestinal-type-mucinous-carcinoma.html +++ b/concepts/intestinal-type-mucinous-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/intestinal-type-stomach-adenocarcinoma.html b/concepts/intestinal-type-stomach-adenocarcinoma.html index 04acbd40d8..8715915819 100644 --- a/concepts/intestinal-type-stomach-adenocarcinoma.html +++ b/concepts/intestinal-type-stomach-adenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/intimal-sarcoma.html b/concepts/intimal-sarcoma.html index 7366767525..d972412857 100644 --- a/concepts/intimal-sarcoma.html +++ b/concepts/intimal-sarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/intraductal-papillary-mucinous-neoplasm.html b/concepts/intraductal-papillary-mucinous-neoplasm.html index 3a2fe12e45..a16059097b 100644 --- a/concepts/intraductal-papillary-mucinous-neoplasm.html +++ b/concepts/intraductal-papillary-mucinous-neoplasm.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/intrahepatic-cholangiocarcinoma.html b/concepts/intrahepatic-cholangiocarcinoma.html index 9f8854222f..f5231e7b19 100644 --- a/concepts/intrahepatic-cholangiocarcinoma.html +++ b/concepts/intrahepatic-cholangiocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/intramuscular-injection.html b/concepts/intramuscular-injection.html index 579c91537f..0a23753180 100644 --- a/concepts/intramuscular-injection.html +++ b/concepts/intramuscular-injection.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/intravascular-large-b-cell-lymphoma.html b/concepts/intravascular-large-b-cell-lymphoma.html index 12cfd73b51..ed62505c8d 100644 --- a/concepts/intravascular-large-b-cell-lymphoma.html +++ b/concepts/intravascular-large-b-cell-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/intravenous-therapy.html b/concepts/intravenous-therapy.html index 84a9e547df..a3561bbeab 100644 --- a/concepts/intravenous-therapy.html +++ b/concepts/intravenous-therapy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/intravesical-therapy.html b/concepts/intravesical-therapy.html index caf1e0bd99..2f533ab3c9 100644 --- a/concepts/intravesical-therapy.html +++ b/concepts/intravesical-therapy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/invasive-breast-carcinoma.html b/concepts/invasive-breast-carcinoma.html index 6f2ff1ea79..37466cd182 100644 --- a/concepts/invasive-breast-carcinoma.html +++ b/concepts/invasive-breast-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/invasive-hydatidiform-mole.html b/concepts/invasive-hydatidiform-mole.html index 84328ef907..dddf3479f0 100644 --- a/concepts/invasive-hydatidiform-mole.html +++ b/concepts/invasive-hydatidiform-mole.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/inverted-urothelial-papilloma.html b/concepts/inverted-urothelial-papilloma.html index c97749ba1d..5aa0a668d6 100644 --- a/concepts/inverted-urothelial-papilloma.html +++ b/concepts/inverted-urothelial-papilloma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/iran-ncc.html b/concepts/iran-ncc.html index 816b96b445..1fa86b106a 100644 --- a/concepts/iran-ncc.html +++ b/concepts/iran-ncc.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ireland-nrg.html b/concepts/ireland-nrg.html index 03aac7c8bb..fab5f89262 100644 --- a/concepts/ireland-nrg.html +++ b/concepts/ireland-nrg.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/irinotecan.html b/concepts/irinotecan.html index ad93dec80e..6c70da1ff3 100644 --- a/concepts/irinotecan.html +++ b/concepts/irinotecan.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ixabepilone.html b/concepts/ixabepilone.html index 297c7f5b37..dec1335336 100644 --- a/concepts/ixabepilone.html +++ b/concepts/ixabepilone.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/johnson-and-johnson.html b/concepts/johnson-and-johnson.html index 4b58851c3f..50d4d4f12d 100644 --- a/concepts/johnson-and-johnson.html +++ b/concepts/johnson-and-johnson.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/jonsson-comprehensive-cancer-center.html b/concepts/jonsson-comprehensive-cancer-center.html index d00050dfbf..d507ea1dea 100644 --- a/concepts/jonsson-comprehensive-cancer-center.html +++ b/concepts/jonsson-comprehensive-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/jordan-ncc.html b/concepts/jordan-ncc.html index 4494bfdf61..6bb70e47af 100644 --- a/concepts/jordan-ncc.html +++ b/concepts/jordan-ncc.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/journal-of-clinical-oncology.html b/concepts/journal-of-clinical-oncology.html index e29840ad42..7c1651d6d0 100644 --- a/concepts/journal-of-clinical-oncology.html +++ b/concepts/journal-of-clinical-oncology.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/journal-of-the-national-cancer-institute.html b/concepts/journal-of-the-national-cancer-institute.html index a47c4c5aea..3e316471e7 100644 --- a/concepts/journal-of-the-national-cancer-institute.html +++ b/concepts/journal-of-the-national-cancer-institute.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/juvenile-myelomonocytic-leukemia.html b/concepts/juvenile-myelomonocytic-leukemia.html index 293c54e161..ef7b1df281 100644 --- a/concepts/juvenile-myelomonocytic-leukemia.html +++ b/concepts/juvenile-myelomonocytic-leukemia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/juvenile-secretory-carcinoma-of-the-breast.html b/concepts/juvenile-secretory-carcinoma-of-the-breast.html index 9271e53b27..4017cb174c 100644 --- a/concepts/juvenile-secretory-carcinoma-of-the-breast.html +++ b/concepts/juvenile-secretory-carcinoma-of-the-breast.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/kaposi-sarcoma.html b/concepts/kaposi-sarcoma.html index 43fce0f14c..eed583005f 100644 --- a/concepts/kaposi-sarcoma.html +++ b/concepts/kaposi-sarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/kazakhstan-ncc.html b/concepts/kazakhstan-ncc.html index 57ff521c4d..b97cbdfe27 100644 --- a/concepts/kazakhstan-ncc.html +++ b/concepts/kazakhstan-ncc.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/kcr-ug.html b/concepts/kcr-ug.html index 4f105ea1df..48818a2736 100644 --- a/concepts/kcr-ug.html +++ b/concepts/kcr-ug.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/kecr-kn.html b/concepts/kecr-kn.html index 316e243062..3ee9e9791c 100644 --- a/concepts/kecr-kn.html +++ b/concepts/kecr-kn.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/kegg.html b/concepts/kegg.html index 2a595b18e2..d304ce07ee 100644 --- a/concepts/kegg.html +++ b/concepts/kegg.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/kidney-cancer-subreddit.html b/concepts/kidney-cancer-subreddit.html index 6662fbaa85..1d37fb3c91 100644 --- a/concepts/kidney-cancer-subreddit.html +++ b/concepts/kidney-cancer-subreddit.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/kidney.html b/concepts/kidney.html index 0ad204c9ad..205f0c9d1f 100644 --- a/concepts/kidney.html +++ b/concepts/kidney.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/kncr-kn.html b/concepts/kncr-kn.html index 75ee970b44..0b1e519214 100644 --- a/concepts/kncr-kn.html +++ b/concepts/kncr-kn.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/knight-cancer-institute.html b/concepts/knight-cancer-institute.html index 1327c481ff..69376c4c56 100644 --- a/concepts/knight-cancer-institute.html +++ b/concepts/knight-cancer-institute.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/kom-op-tegen-kanker.html b/concepts/kom-op-tegen-kanker.html index 09ac073925..017b0addd0 100644 --- a/concepts/kom-op-tegen-kanker.html +++ b/concepts/kom-op-tegen-kanker.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/kscr-gh.html b/concepts/kscr-gh.html index 6642cafc03..13d5ec3054 100644 --- a/concepts/kscr-gh.html +++ b/concepts/kscr-gh.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/langerhans-cell-histiocytosis.html b/concepts/langerhans-cell-histiocytosis.html index 3e357c2799..465aeead92 100644 --- a/concepts/langerhans-cell-histiocytosis.html +++ b/concepts/langerhans-cell-histiocytosis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/langerhans-cell-sarcoma.html b/concepts/langerhans-cell-sarcoma.html index c8fc412c10..9ba7395da1 100644 --- a/concepts/langerhans-cell-sarcoma.html +++ b/concepts/langerhans-cell-sarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/large-b-cell-lymphoma-with-irf4-rearrangement.html b/concepts/large-b-cell-lymphoma-with-irf4-rearrangement.html index 11de911bcc..ad5f678045 100644 --- a/concepts/large-b-cell-lymphoma-with-irf4-rearrangement.html +++ b/concepts/large-b-cell-lymphoma-with-irf4-rearrangement.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/large-cell-anaplastic-medulloblastoma.html b/concepts/large-cell-anaplastic-medulloblastoma.html index a2e04f025f..ad96bd746a 100644 --- a/concepts/large-cell-anaplastic-medulloblastoma.html +++ b/concepts/large-cell-anaplastic-medulloblastoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/large-cell-lung-carcinoma-with-rhabdoid-phenotype.html b/concepts/large-cell-lung-carcinoma-with-rhabdoid-phenotype.html index 1fd44bfb56..ffcdeac7e8 100644 --- a/concepts/large-cell-lung-carcinoma-with-rhabdoid-phenotype.html +++ b/concepts/large-cell-lung-carcinoma-with-rhabdoid-phenotype.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/large-cell-lung-carcinoma.html b/concepts/large-cell-lung-carcinoma.html index 1dc2fd14a7..0349b4cdc5 100644 --- a/concepts/large-cell-lung-carcinoma.html +++ b/concepts/large-cell-lung-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/large-cell-neuroendocrine-carcinoma.html b/concepts/large-cell-neuroendocrine-carcinoma.html index 43031c6bcd..326cfb8670 100644 --- a/concepts/large-cell-neuroendocrine-carcinoma.html +++ b/concepts/large-cell-neuroendocrine-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/larotaxel.html b/concepts/larotaxel.html index 8a2d3ee5d0..0cf5169808 100644 --- a/concepts/larotaxel.html +++ b/concepts/larotaxel.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/laryngeal-cancer.html b/concepts/laryngeal-cancer.html index 3083421be4..db2ee63dab 100644 --- a/concepts/laryngeal-cancer.html +++ b/concepts/laryngeal-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/laryngectomy.html b/concepts/laryngectomy.html index b3e5dede62..92b8d5857b 100644 --- a/concepts/laryngectomy.html +++ b/concepts/laryngectomy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/larynx-squamous-cell-carcinoma.html b/concepts/larynx-squamous-cell-carcinoma.html index bf4139a93e..c87673b1a8 100644 --- a/concepts/larynx-squamous-cell-carcinoma.html +++ b/concepts/larynx-squamous-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/laura-and-isaac-perlmutter-cancer-center-at-nyu-langone-health.html b/concepts/laura-and-isaac-perlmutter-cancer-center-at-nyu-langone-health.html index 1619c2f120..d6f15f6487 100644 --- a/concepts/laura-and-isaac-perlmutter-cancer-center-at-nyu-langone-health.html +++ b/concepts/laura-and-isaac-perlmutter-cancer-center-at-nyu-langone-health.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/leiomyoma.html b/concepts/leiomyoma.html index 7e975d0e2c..94f0437cdd 100644 --- a/concepts/leiomyoma.html +++ b/concepts/leiomyoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/leiomyosarcoma.html b/concepts/leiomyosarcoma.html index 873a6e4bb3..3d400ebf0d 100644 --- a/concepts/leiomyosarcoma.html +++ b/concepts/leiomyosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/lentigo-maligna-melanoma.html b/concepts/lentigo-maligna-melanoma.html index 85a21a069a..4293b2c545 100644 --- a/concepts/lentigo-maligna-melanoma.html +++ b/concepts/lentigo-maligna-melanoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/lenvatinib.html b/concepts/lenvatinib.html index 523199849b..bd1d0bc056 100644 --- a/concepts/lenvatinib.html +++ b/concepts/lenvatinib.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/leukapheresis.html b/concepts/leukapheresis.html index c4d8eb56f0..6f6e6b3395 100644 --- a/concepts/leukapheresis.html +++ b/concepts/leukapheresis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/leukemia-and-lymphoma-society.html b/concepts/leukemia-and-lymphoma-society.html index cd2b7f21c4..87c0eba791 100644 --- a/concepts/leukemia-and-lymphoma-society.html +++ b/concepts/leukemia-and-lymphoma-society.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/leukemia.html b/concepts/leukemia.html index a603df5400..f0c5d2f1ab 100644 --- a/concepts/leukemia.html +++ b/concepts/leukemia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/liposarcoma.html b/concepts/liposarcoma.html index 81bc5b543b..abb1149bd0 100644 --- a/concepts/liposarcoma.html +++ b/concepts/liposarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/lithuania-ncc.html b/concepts/lithuania-ncc.html index 0a199f033f..09a160c929 100644 --- a/concepts/lithuania-ncc.html +++ b/concepts/lithuania-ncc.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/liver-angiosarcoma.html b/concepts/liver-angiosarcoma.html index 8a7405f34b..66914f1ac3 100644 --- a/concepts/liver-angiosarcoma.html +++ b/concepts/liver-angiosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/liver-transplant.html b/concepts/liver-transplant.html index bb5cdb25be..06e6d420dd 100644 --- a/concepts/liver-transplant.html +++ b/concepts/liver-transplant.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/liver.html b/concepts/liver.html index c50861c922..b0be6ef016 100644 --- a/concepts/liver.html +++ b/concepts/liver.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/lomustine.html b/concepts/lomustine.html index 57e8f7f04b..3f17bd0a8e 100644 --- a/concepts/lomustine.html +++ b/concepts/lomustine.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/low-dose-spiral-computed-tomography.html b/concepts/low-dose-spiral-computed-tomography.html index 325c5ee324..01c6f59fc5 100644 --- a/concepts/low-dose-spiral-computed-tomography.html +++ b/concepts/low-dose-spiral-computed-tomography.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/low-grade-central-osteosarcoma.html b/concepts/low-grade-central-osteosarcoma.html index cf6cc0e3d0..512ecfca9e 100644 --- a/concepts/low-grade-central-osteosarcoma.html +++ b/concepts/low-grade-central-osteosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/low-grade-endometrial-stromal-sarcoma.html b/concepts/low-grade-endometrial-stromal-sarcoma.html index 7a2908af53..18e00d27f9 100644 --- a/concepts/low-grade-endometrial-stromal-sarcoma.html +++ b/concepts/low-grade-endometrial-stromal-sarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/low-grade-fibromyxoid-sarcoma.html b/concepts/low-grade-fibromyxoid-sarcoma.html index 943449a8c3..2fc0878f7e 100644 --- a/concepts/low-grade-fibromyxoid-sarcoma.html +++ b/concepts/low-grade-fibromyxoid-sarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/low-grade-glioma-nos.html b/concepts/low-grade-glioma-nos.html index f3783796ce..507cfcf455 100644 --- a/concepts/low-grade-glioma-nos.html +++ b/concepts/low-grade-glioma-nos.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/low-grade-neuroepithelial-tumor.html b/concepts/low-grade-neuroepithelial-tumor.html index 10384b763b..2d2ec86036 100644 --- a/concepts/low-grade-neuroepithelial-tumor.html +++ b/concepts/low-grade-neuroepithelial-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/low-grade-serous-ovarian-cancer.html b/concepts/low-grade-serous-ovarian-cancer.html index 00992c9ee6..e84592cc34 100644 --- a/concepts/low-grade-serous-ovarian-cancer.html +++ b/concepts/low-grade-serous-ovarian-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/lumpectomy.html b/concepts/lumpectomy.html index 0136f298f9..781fcd50a7 100644 --- a/concepts/lumpectomy.html +++ b/concepts/lumpectomy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/lung-adenocarcinoma-in-situ.html b/concepts/lung-adenocarcinoma-in-situ.html index a87d191b05..354d3a09e5 100644 --- a/concepts/lung-adenocarcinoma-in-situ.html +++ b/concepts/lung-adenocarcinoma-in-situ.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/lung-adenocarcinoma.html b/concepts/lung-adenocarcinoma.html index adb912c1ac..e06098300d 100644 --- a/concepts/lung-adenocarcinoma.html +++ b/concepts/lung-adenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/lung-adenosquamous-carcinoma.html b/concepts/lung-adenosquamous-carcinoma.html index df5c50ed9c..6422a117c5 100644 --- a/concepts/lung-adenosquamous-carcinoma.html +++ b/concepts/lung-adenosquamous-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/lung-cancer-subreddit.html b/concepts/lung-cancer-subreddit.html index e028d70590..7c60319126 100644 --- a/concepts/lung-cancer-subreddit.html +++ b/concepts/lung-cancer-subreddit.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/lung-carcinoid.html b/concepts/lung-carcinoid.html index 26d8cce952..1d86de2690 100644 --- a/concepts/lung-carcinoid.html +++ b/concepts/lung-carcinoid.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/lung-neuroendocrine-tumor.html b/concepts/lung-neuroendocrine-tumor.html index 289247d604..85ddade0b8 100644 --- a/concepts/lung-neuroendocrine-tumor.html +++ b/concepts/lung-neuroendocrine-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/lung-squamous-cell-carcinoma.html b/concepts/lung-squamous-cell-carcinoma.html index 40c93c70b6..42400f4390 100644 --- a/concepts/lung-squamous-cell-carcinoma.html +++ b/concepts/lung-squamous-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/lung.html b/concepts/lung.html index 9c4824a880..09e406d8c8 100644 --- a/concepts/lung.html +++ b/concepts/lung.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/lymphedema-sleeve.html b/concepts/lymphedema-sleeve.html index efebd25848..47b927f454 100644 --- a/concepts/lymphedema-sleeve.html +++ b/concepts/lymphedema-sleeve.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/lymphocyte-depleted-classical-hodgkin-lymphoma.html b/concepts/lymphocyte-depleted-classical-hodgkin-lymphoma.html index 07d584432f..30d8865b45 100644 --- a/concepts/lymphocyte-depleted-classical-hodgkin-lymphoma.html +++ b/concepts/lymphocyte-depleted-classical-hodgkin-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/lymphocyte-rich-classical-hodgkin-lymphoma.html b/concepts/lymphocyte-rich-classical-hodgkin-lymphoma.html index 60d9f81439..3b9b1be6f3 100644 --- a/concepts/lymphocyte-rich-classical-hodgkin-lymphoma.html +++ b/concepts/lymphocyte-rich-classical-hodgkin-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/lymphoepithelioma-like-carcinoma-of-the-lung.html b/concepts/lymphoepithelioma-like-carcinoma-of-the-lung.html index 1d961e9228..f2880d1324 100644 --- a/concepts/lymphoepithelioma-like-carcinoma-of-the-lung.html +++ b/concepts/lymphoepithelioma-like-carcinoma-of-the-lung.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/lymphoid-atypical.html b/concepts/lymphoid-atypical.html index 608db8e8ed..1e6eccc36d 100644 --- a/concepts/lymphoid-atypical.html +++ b/concepts/lymphoid-atypical.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/lymphoid-benign.html b/concepts/lymphoid-benign.html index e8e8cb987f..8ea0cb03a0 100644 --- a/concepts/lymphoid-benign.html +++ b/concepts/lymphoid-benign.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/lymphoid-neoplasm.html b/concepts/lymphoid-neoplasm.html index 58113a834e..ca581ed0e6 100644 --- a/concepts/lymphoid-neoplasm.html +++ b/concepts/lymphoid-neoplasm.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/lymphoma-subreddit.html b/concepts/lymphoma-subreddit.html index c6c91aeff3..dd24b23ac8 100644 --- a/concepts/lymphoma-subreddit.html +++ b/concepts/lymphoma-subreddit.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/lymphoma.html b/concepts/lymphoma.html index c193bd1800..cd81d45ded 100644 --- a/concepts/lymphoma.html +++ b/concepts/lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/lymphomatoid-granulomatosis.html b/concepts/lymphomatoid-granulomatosis.html index c9a70bda90..86427c472b 100644 --- a/concepts/lymphomatoid-granulomatosis.html +++ b/concepts/lymphomatoid-granulomatosis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/lymphomatoid-papulosis.html b/concepts/lymphomatoid-papulosis.html index 172b96d4a7..c021992a28 100644 --- a/concepts/lymphomatoid-papulosis.html +++ b/concepts/lymphomatoid-papulosis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/lymphoplasmacytic-lymphoma.html b/concepts/lymphoplasmacytic-lymphoma.html index 725dc02f04..96d285e43f 100644 --- a/concepts/lymphoplasmacytic-lymphoma.html +++ b/concepts/lymphoplasmacytic-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/male-breast-cancer.html b/concepts/male-breast-cancer.html index c8ccfd5fb5..427a8ab2e9 100644 --- a/concepts/male-breast-cancer.html +++ b/concepts/male-breast-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/malignant-glomus-tumor.html b/concepts/malignant-glomus-tumor.html index d5110db6d0..b6f4234a08 100644 --- a/concepts/malignant-glomus-tumor.html +++ b/concepts/malignant-glomus-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/malignant-lymphoma.html b/concepts/malignant-lymphoma.html index f8c6794bc2..c5be80bf5f 100644 --- a/concepts/malignant-lymphoma.html +++ b/concepts/malignant-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/malignant-nonepithelial-tumor-of-the-liver.html b/concepts/malignant-nonepithelial-tumor-of-the-liver.html index 87dd874b2e..5ced73a88b 100644 --- a/concepts/malignant-nonepithelial-tumor-of-the-liver.html +++ b/concepts/malignant-nonepithelial-tumor-of-the-liver.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/malignant-peripheral-nerve-sheath-tumor.html b/concepts/malignant-peripheral-nerve-sheath-tumor.html index cf022e88c7..d388f41909 100644 --- a/concepts/malignant-peripheral-nerve-sheath-tumor.html +++ b/concepts/malignant-peripheral-nerve-sheath-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/malignant-phyllodes-tumor-of-the-breast.html b/concepts/malignant-phyllodes-tumor-of-the-breast.html index 7ab663328d..c4d9114679 100644 --- a/concepts/malignant-phyllodes-tumor-of-the-breast.html +++ b/concepts/malignant-phyllodes-tumor-of-the-breast.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/malignant-rhabdoid-tumor-of-the-liver.html b/concepts/malignant-rhabdoid-tumor-of-the-liver.html index ce4ae739d4..878f75948e 100644 --- a/concepts/malignant-rhabdoid-tumor-of-the-liver.html +++ b/concepts/malignant-rhabdoid-tumor-of-the-liver.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/malignant-teratoma.html b/concepts/malignant-teratoma.html index a7851dada7..568b418706 100644 --- a/concepts/malignant-teratoma.html +++ b/concepts/malignant-teratoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/malignant-tumor.html b/concepts/malignant-tumor.html index e13e228269..bd1fa33cb1 100644 --- a/concepts/malignant-tumor.html +++ b/concepts/malignant-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mammary-analogue-secretory-carcinoma-of-salivary-gland-origin.html b/concepts/mammary-analogue-secretory-carcinoma-of-salivary-gland-origin.html index 1e6ed4d572..b8e13ff41f 100644 --- a/concepts/mammary-analogue-secretory-carcinoma-of-salivary-gland-origin.html +++ b/concepts/mammary-analogue-secretory-carcinoma-of-salivary-gland-origin.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mammogram.html b/concepts/mammogram.html index 6698d0d21d..c829ecdf73 100644 --- a/concepts/mammogram.html +++ b/concepts/mammogram.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mantle-cell-lymphoma.html b/concepts/mantle-cell-lymphoma.html index 29e4d133f5..443e30cf76 100644 --- a/concepts/mantle-cell-lymphoma.html +++ b/concepts/mantle-cell-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/margin-probe.html b/concepts/margin-probe.html index e6d649d011..b4d550ce17 100644 --- a/concepts/margin-probe.html +++ b/concepts/margin-probe.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/marginal-zone-lymphoma.html b/concepts/marginal-zone-lymphoma.html index 0e09f1c6df..3fc7c2b4dc 100644 --- a/concepts/marginal-zone-lymphoma.html +++ b/concepts/marginal-zone-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/marijuana.html b/concepts/marijuana.html index 0aba5bc2ca..2836ae8461 100644 --- a/concepts/marijuana.html +++ b/concepts/marijuana.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/markey-cancer-center.html b/concepts/markey-cancer-center.html index 76322a86e9..87e3412fb3 100644 --- a/concepts/markey-cancer-center.html +++ b/concepts/markey-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/masonic-cancer-center.html b/concepts/masonic-cancer-center.html index ad81bc4cb0..06bc5509c2 100644 --- a/concepts/masonic-cancer-center.html +++ b/concepts/masonic-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/massage.html b/concepts/massage.html index 7ab5bf8e39..59d3148a11 100644 --- a/concepts/massage.html +++ b/concepts/massage.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/massey-cancer-center.html b/concepts/massey-cancer-center.html index 1839776eca..b4c18e43ea 100644 --- a/concepts/massey-cancer-center.html +++ b/concepts/massey-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/massgeneral.html b/concepts/massgeneral.html index 16fdaa6cae..17b8ed48de 100644 --- a/concepts/massgeneral.html +++ b/concepts/massgeneral.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mast-cell-leukemia.html b/concepts/mast-cell-leukemia.html index 8bdb67509c..b403550e45 100644 --- a/concepts/mast-cell-leukemia.html +++ b/concepts/mast-cell-leukemia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mast-cell-sarcoma.html b/concepts/mast-cell-sarcoma.html index 675152b105..fca1557dd5 100644 --- a/concepts/mast-cell-sarcoma.html +++ b/concepts/mast-cell-sarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mastectomy.html b/concepts/mastectomy.html index 774442a988..918abaed3c 100644 --- a/concepts/mastectomy.html +++ b/concepts/mastectomy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mastocytosis.html b/concepts/mastocytosis.html index d9130ea7c5..a399b2ecce 100644 --- a/concepts/mastocytosis.html +++ b/concepts/mastocytosis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mature-b-cell-neoplasms.html b/concepts/mature-b-cell-neoplasms.html index 80dd116cdd..6d55678016 100644 --- a/concepts/mature-b-cell-neoplasms.html +++ b/concepts/mature-b-cell-neoplasms.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mature-t-and-nk-neoplasms.html b/concepts/mature-t-and-nk-neoplasms.html index bb0aeba2bc..c10888e54a 100644 --- a/concepts/mature-t-and-nk-neoplasms.html +++ b/concepts/mature-t-and-nk-neoplasms.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mature-teratoma-cns-brain.html b/concepts/mature-teratoma-cns-brain.html index bf69c42d36..b554499f8b 100644 --- a/concepts/mature-teratoma-cns-brain.html +++ b/concepts/mature-teratoma-cns-brain.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mature-teratoma-vulva-vagina.html b/concepts/mature-teratoma-vulva-vagina.html index e67cedeb03..2fa4eb840e 100644 --- a/concepts/mature-teratoma-vulva-vagina.html +++ b/concepts/mature-teratoma-vulva-vagina.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mature-teratoma.html b/concepts/mature-teratoma.html index 122c62f45b..047f7c1af7 100644 --- a/concepts/mature-teratoma.html +++ b/concepts/mature-teratoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mayo-clinic-jacksonville.html b/concepts/mayo-clinic-jacksonville.html index 6bed4ffe79..1bbe130c68 100644 --- a/concepts/mayo-clinic-jacksonville.html +++ b/concepts/mayo-clinic-jacksonville.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mayo-clinic-phoenix.html b/concepts/mayo-clinic-phoenix.html index c68e557aa2..182d211e85 100644 --- a/concepts/mayo-clinic-phoenix.html +++ b/concepts/mayo-clinic-phoenix.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mayo-clinic-rochester.html b/concepts/mayo-clinic-rochester.html index babe108306..ab2ed8307a 100644 --- a/concepts/mayo-clinic-rochester.html +++ b/concepts/mayo-clinic-rochester.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mays-cancer-center-at-ut-health-san-antonio.html b/concepts/mays-cancer-center-at-ut-health-san-antonio.html index 384aa51b5c..384f8e36c0 100644 --- a/concepts/mays-cancer-center-at-ut-health-san-antonio.html +++ b/concepts/mays-cancer-center-at-ut-health-san-antonio.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mcr-tz.html b/concepts/mcr-tz.html index 91990c048d..4ef25b0d60 100644 --- a/concepts/mcr-tz.html +++ b/concepts/mcr-tz.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mdanderson.html b/concepts/mdanderson.html index ad5240877a..346ef3b40a 100644 --- a/concepts/mdanderson.html +++ b/concepts/mdanderson.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mds-mpn-unclassifiable.html b/concepts/mds-mpn-unclassifiable.html index 60753ad73a..057f740be2 100644 --- a/concepts/mds-mpn-unclassifiable.html +++ b/concepts/mds-mpn-unclassifiable.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mds-mpn-with-ring-sideroblasts-and-thrombocytosis.html b/concepts/mds-mpn-with-ring-sideroblasts-and-thrombocytosis.html index 1e4b11e194..f9a64a42fb 100644 --- a/concepts/mds-mpn-with-ring-sideroblasts-and-thrombocytosis.html +++ b/concepts/mds-mpn-with-ring-sideroblasts-and-thrombocytosis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mds-unclassifiable.html b/concepts/mds-unclassifiable.html index 93a273a957..07740e3c4d 100644 --- a/concepts/mds-unclassifiable.html +++ b/concepts/mds-unclassifiable.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mds-with-excess-blasts-1.html b/concepts/mds-with-excess-blasts-1.html index f40bede88d..2793cf95cf 100644 --- a/concepts/mds-with-excess-blasts-1.html +++ b/concepts/mds-with-excess-blasts-1.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mds-with-excess-blasts-2.html b/concepts/mds-with-excess-blasts-2.html index d55b1af250..a3a8102841 100644 --- a/concepts/mds-with-excess-blasts-2.html +++ b/concepts/mds-with-excess-blasts-2.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mds-with-excess-blasts.html b/concepts/mds-with-excess-blasts.html index 8d7910a969..4f6205b78a 100644 --- a/concepts/mds-with-excess-blasts.html +++ b/concepts/mds-with-excess-blasts.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mds-with-isolated-del5q.html b/concepts/mds-with-isolated-del5q.html index 2b248a43b1..448962bebb 100644 --- a/concepts/mds-with-isolated-del5q.html +++ b/concepts/mds-with-isolated-del5q.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mds-with-multilineage-dysplasia.html b/concepts/mds-with-multilineage-dysplasia.html index ece634befa..46cc7c8fe3 100644 --- a/concepts/mds-with-multilineage-dysplasia.html +++ b/concepts/mds-with-multilineage-dysplasia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mds-with-ring-sideroblasts-and-multilineage-dysplasia.html b/concepts/mds-with-ring-sideroblasts-and-multilineage-dysplasia.html index 60d838d3a2..1575b8a8be 100644 --- a/concepts/mds-with-ring-sideroblasts-and-multilineage-dysplasia.html +++ b/concepts/mds-with-ring-sideroblasts-and-multilineage-dysplasia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mds-with-ring-sideroblasts-and-single-lineage-dysplasia.html b/concepts/mds-with-ring-sideroblasts-and-single-lineage-dysplasia.html index 87195c611b..96f3d529c3 100644 --- a/concepts/mds-with-ring-sideroblasts-and-single-lineage-dysplasia.html +++ b/concepts/mds-with-ring-sideroblasts-and-single-lineage-dysplasia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mds-with-ring-sideroblasts.html b/concepts/mds-with-ring-sideroblasts.html index a58382f70a..a2b3bef69b 100644 --- a/concepts/mds-with-ring-sideroblasts.html +++ b/concepts/mds-with-ring-sideroblasts.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mds-with-single-lineage-dysplasia.html b/concepts/mds-with-single-lineage-dysplasia.html index 6a7bad4792..b318bca27d 100644 --- a/concepts/mds-with-single-lineage-dysplasia.html +++ b/concepts/mds-with-single-lineage-dysplasia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mediastinoscopy.html b/concepts/mediastinoscopy.html index 1145d4837f..1c00110fcd 100644 --- a/concepts/mediastinoscopy.html +++ b/concepts/mediastinoscopy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/meditation.html b/concepts/meditation.html index 033dafb2a6..4600a56c51 100644 --- a/concepts/meditation.html +++ b/concepts/meditation.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/medlars.html b/concepts/medlars.html index 8034258407..1d7ad5f08e 100644 --- a/concepts/medlars.html +++ b/concepts/medlars.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/medline.html b/concepts/medline.html index e003703365..fcc5a5f067 100644 --- a/concepts/medline.html +++ b/concepts/medline.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/medlineplus.html b/concepts/medlineplus.html index c63ef0d1d1..3db60e31c3 100644 --- a/concepts/medlineplus.html +++ b/concepts/medlineplus.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/medullary-carcinoma-of-the-colon.html b/concepts/medullary-carcinoma-of-the-colon.html index cd693d8d58..47d0137502 100644 --- a/concepts/medullary-carcinoma-of-the-colon.html +++ b/concepts/medullary-carcinoma-of-the-colon.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/medullary-thyroid-cancer.html b/concepts/medullary-thyroid-cancer.html index 6f39dee196..e2819cf13d 100644 --- a/concepts/medullary-thyroid-cancer.html +++ b/concepts/medullary-thyroid-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/medulloblastoma-with-extensive-nodularity.html b/concepts/medulloblastoma-with-extensive-nodularity.html index c3a2ab9c5d..3198210fc2 100644 --- a/concepts/medulloblastoma-with-extensive-nodularity.html +++ b/concepts/medulloblastoma-with-extensive-nodularity.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/medulloblastoma.html b/concepts/medulloblastoma.html index cb4e0c143e..2238743d8e 100644 --- a/concepts/medulloblastoma.html +++ b/concepts/medulloblastoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/medulloepithelioma.html b/concepts/medulloepithelioma.html index fa6c5b2af9..2d3e1a624d 100644 --- a/concepts/medulloepithelioma.html +++ b/concepts/medulloepithelioma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/medullomyoblastoma.html b/concepts/medullomyoblastoma.html index 3b4d20cbb2..4f4a3e20c5 100644 --- a/concepts/medullomyoblastoma.html +++ b/concepts/medullomyoblastoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/melanocytoma.html b/concepts/melanocytoma.html index 9f57cebf6f..ea77d791c9 100644 --- a/concepts/melanocytoma.html +++ b/concepts/melanocytoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/melanoma-of-unknown-primary.html b/concepts/melanoma-of-unknown-primary.html index cbcca1766d..faf63a7bc6 100644 --- a/concepts/melanoma-of-unknown-primary.html +++ b/concepts/melanoma-of-unknown-primary.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/melanoma.html b/concepts/melanoma.html index 471dc43ce4..c3d91c4bf4 100644 --- a/concepts/melanoma.html +++ b/concepts/melanoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/melanotic-medulloblastoma.html b/concepts/melanotic-medulloblastoma.html index c95d9fcdec..d1d789edcb 100644 --- a/concepts/melanotic-medulloblastoma.html +++ b/concepts/melanotic-medulloblastoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/melanotic-schwannoma.html b/concepts/melanotic-schwannoma.html index bbb1cb4744..66c25b2b37 100644 --- a/concepts/melanotic-schwannoma.html +++ b/concepts/melanotic-schwannoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/melphalan-flufenamide.html b/concepts/melphalan-flufenamide.html index 86b93e4322..3297d2c61f 100644 --- a/concepts/melphalan-flufenamide.html +++ b/concepts/melphalan-flufenamide.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/melphalan.html b/concepts/melphalan.html index fe58119db9..e767717842 100644 --- a/concepts/melphalan.html +++ b/concepts/melphalan.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/memorial-sloan-kettering-cancer-center.html b/concepts/memorial-sloan-kettering-cancer-center.html index 6646fda3bf..78ab497b48 100644 --- a/concepts/memorial-sloan-kettering-cancer-center.html +++ b/concepts/memorial-sloan-kettering-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/meningioma.html b/concepts/meningioma.html index 5fa6867fa9..81eda3d2e5 100644 --- a/concepts/meningioma.html +++ b/concepts/meningioma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/meningothelial-tumor.html b/concepts/meningothelial-tumor.html index c2cea9f6f7..10faa624ad 100644 --- a/concepts/meningothelial-tumor.html +++ b/concepts/meningothelial-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mercaptopurine.html b/concepts/mercaptopurine.html index eee7122de6..a4a0fc0b7d 100644 --- a/concepts/mercaptopurine.html +++ b/concepts/mercaptopurine.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/merck-and-co.html b/concepts/merck-and-co.html index 21e05a7b07..f84388fa9b 100644 --- a/concepts/merck-and-co.html +++ b/concepts/merck-and-co.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/merck-group.html b/concepts/merck-group.html index 51ccfa6d44..14b59e469a 100644 --- a/concepts/merck-group.html +++ b/concepts/merck-group.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/merkel-cell-carcinoma.html b/concepts/merkel-cell-carcinoma.html index 32482d5e8b..3043bc185a 100644 --- a/concepts/merkel-cell-carcinoma.html +++ b/concepts/merkel-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mesenchymal-chondrosarcoma-of-the-cns.html b/concepts/mesenchymal-chondrosarcoma-of-the-cns.html index 5dca3d4a19..d588b2c0d2 100644 --- a/concepts/mesenchymal-chondrosarcoma-of-the-cns.html +++ b/concepts/mesenchymal-chondrosarcoma-of-the-cns.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mesenchymal-chondrosarcoma.html b/concepts/mesenchymal-chondrosarcoma.html index a33d4e96c8..e378a0b87f 100644 --- a/concepts/mesenchymal-chondrosarcoma.html +++ b/concepts/mesenchymal-chondrosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mesonephric-carcinoma.html b/concepts/mesonephric-carcinoma.html index fc36e164cb..a34103da59 100644 --- a/concepts/mesonephric-carcinoma.html +++ b/concepts/mesonephric-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mesothelioma.html b/concepts/mesothelioma.html index c1f04cc23d..c20cc5c095 100644 --- a/concepts/mesothelioma.html +++ b/concepts/mesothelioma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/metaplastic-adenocarcinoma-with-spindle-cell-differentiation.html b/concepts/metaplastic-adenocarcinoma-with-spindle-cell-differentiation.html index 8510215a07..858e3dfc5e 100644 --- a/concepts/metaplastic-adenocarcinoma-with-spindle-cell-differentiation.html +++ b/concepts/metaplastic-adenocarcinoma-with-spindle-cell-differentiation.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/metaplastic-adenosquamous-carcinoma.html b/concepts/metaplastic-adenosquamous-carcinoma.html index 7203465e7c..1b6194d83e 100644 --- a/concepts/metaplastic-adenosquamous-carcinoma.html +++ b/concepts/metaplastic-adenosquamous-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/metaplastic-breast-cancer.html b/concepts/metaplastic-breast-cancer.html index 757057a1b1..32aa93e9e4 100644 --- a/concepts/metaplastic-breast-cancer.html +++ b/concepts/metaplastic-breast-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/metaplastic-carcinosarcoma.html b/concepts/metaplastic-carcinosarcoma.html index ff66fba393..f3e792814f 100644 --- a/concepts/metaplastic-carcinosarcoma.html +++ b/concepts/metaplastic-carcinosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/metaplastic-squamous-cell-carcinoma.html b/concepts/metaplastic-squamous-cell-carcinoma.html index dc6ea254b2..be1b3d640f 100644 --- a/concepts/metaplastic-squamous-cell-carcinoma.html +++ b/concepts/metaplastic-squamous-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/methotrexate.html b/concepts/methotrexate.html index 3e41d8ef3c..c05a81cd85 100644 --- a/concepts/methotrexate.html +++ b/concepts/methotrexate.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/miaderm-cream.html b/concepts/miaderm-cream.html index c3800bb4e3..3a14991d0f 100644 --- a/concepts/miaderm-cream.html +++ b/concepts/miaderm-cream.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/microcystic-adnexal-carcinoma.html b/concepts/microcystic-adnexal-carcinoma.html index 6c4c93e7c2..0c652ccb22 100644 --- a/concepts/microcystic-adnexal-carcinoma.html +++ b/concepts/microcystic-adnexal-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/microscope-confocal.html b/concepts/microscope-confocal.html index e5f0e4a5e2..e74dafd2ec 100644 --- a/concepts/microscope-confocal.html +++ b/concepts/microscope-confocal.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/microscope-darkfield.html b/concepts/microscope-darkfield.html index d7cdb05cb3..0e6a0a2b5f 100644 --- a/concepts/microscope-darkfield.html +++ b/concepts/microscope-darkfield.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/microscope-electron.html b/concepts/microscope-electron.html index 608ca1b49b..ec87e42443 100644 --- a/concepts/microscope-electron.html +++ b/concepts/microscope-electron.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/microscope-flourescence.html b/concepts/microscope-flourescence.html index 2e61cff4d0..ed3a71d82f 100644 --- a/concepts/microscope-flourescence.html +++ b/concepts/microscope-flourescence.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/microscope-inverted.html b/concepts/microscope-inverted.html index 21f6e71502..ed7cd89b06 100644 --- a/concepts/microscope-inverted.html +++ b/concepts/microscope-inverted.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/microscope-optical.html b/concepts/microscope-optical.html index c663d4e54a..3f931b5973 100644 --- a/concepts/microscope-optical.html +++ b/concepts/microscope-optical.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/microscope-phasecontrast.html b/concepts/microscope-phasecontrast.html index f0bc69f42a..97373a5a4e 100644 --- a/concepts/microscope-phasecontrast.html +++ b/concepts/microscope-phasecontrast.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/microscope-polarizing.html b/concepts/microscope-polarizing.html index 1269c13e0b..400809175f 100644 --- a/concepts/microscope-polarizing.html +++ b/concepts/microscope-polarizing.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/microscope-scanningprobe.html b/concepts/microscope-scanningprobe.html index 3465e9c882..680c05a155 100644 --- a/concepts/microscope-scanningprobe.html +++ b/concepts/microscope-scanningprobe.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/microscope-stereo.html b/concepts/microscope-stereo.html index 94f433b2f9..5fe70b310a 100644 --- a/concepts/microscope-stereo.html +++ b/concepts/microscope-stereo.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/microscope-ultraviolet.html b/concepts/microscope-ultraviolet.html index f49358286b..898fa70b41 100644 --- a/concepts/microscope-ultraviolet.html +++ b/concepts/microscope-ultraviolet.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/microwave-ablation.html b/concepts/microwave-ablation.html index 8365d7fe6c..304ba65fd8 100644 --- a/concepts/microwave-ablation.html +++ b/concepts/microwave-ablation.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/miscellaneous-brain-tumor.html b/concepts/miscellaneous-brain-tumor.html index de1081298e..8978310a1c 100644 --- a/concepts/miscellaneous-brain-tumor.html +++ b/concepts/miscellaneous-brain-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/miscellaneous-neuroepithelial-tumor.html b/concepts/miscellaneous-neuroepithelial-tumor.html index 36f2ed1975..4508abab51 100644 --- a/concepts/miscellaneous-neuroepithelial-tumor.html +++ b/concepts/miscellaneous-neuroepithelial-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mitobronitol.html b/concepts/mitobronitol.html index 604a340297..081ff66d4e 100644 --- a/concepts/mitobronitol.html +++ b/concepts/mitobronitol.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mitomycin.html b/concepts/mitomycin.html index b75214201d..1be1f0da46 100644 --- a/concepts/mitomycin.html +++ b/concepts/mitomycin.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mitoxantrone.html b/concepts/mitoxantrone.html index 71d0e98393..5b89d57978 100644 --- a/concepts/mitoxantrone.html +++ b/concepts/mitoxantrone.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mixed-ampullary-carcinoma.html b/concepts/mixed-ampullary-carcinoma.html index 2860e85654..ccb691ee98 100644 --- a/concepts/mixed-ampullary-carcinoma.html +++ b/concepts/mixed-ampullary-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mixed-cancer-types.html b/concepts/mixed-cancer-types.html index 1070987286..ace73d5270 100644 --- a/concepts/mixed-cancer-types.html +++ b/concepts/mixed-cancer-types.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mixed-cellularity-classical-hodgkin-lymphoma.html b/concepts/mixed-cellularity-classical-hodgkin-lymphoma.html index 1532877e23..7229f5c4c9 100644 --- a/concepts/mixed-cellularity-classical-hodgkin-lymphoma.html +++ b/concepts/mixed-cellularity-classical-hodgkin-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mixed-cervical-carcinoma.html b/concepts/mixed-cervical-carcinoma.html index 94cb8eeea7..5a7b4fdf0d 100644 --- a/concepts/mixed-cervical-carcinoma.html +++ b/concepts/mixed-cervical-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mixed-germ-cell-tumor-cns-brain.html b/concepts/mixed-germ-cell-tumor-cns-brain.html index 10808012ea..ae30e7f63a 100644 --- a/concepts/mixed-germ-cell-tumor-cns-brain.html +++ b/concepts/mixed-germ-cell-tumor-cns-brain.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mixed-germ-cell-tumor-testis.html b/concepts/mixed-germ-cell-tumor-testis.html index 95ab216e3a..4ebc2582f3 100644 --- a/concepts/mixed-germ-cell-tumor-testis.html +++ b/concepts/mixed-germ-cell-tumor-testis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mixed-germ-cell-tumor-vulva-vagina.html b/concepts/mixed-germ-cell-tumor-vulva-vagina.html index 2198a0b8ec..0623edc421 100644 --- a/concepts/mixed-germ-cell-tumor-vulva-vagina.html +++ b/concepts/mixed-germ-cell-tumor-vulva-vagina.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mixed-germ-cell-tumor.html b/concepts/mixed-germ-cell-tumor.html index 4838787cac..e478e4af20 100644 --- a/concepts/mixed-germ-cell-tumor.html +++ b/concepts/mixed-germ-cell-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mixed-ovarian-carcinoma.html b/concepts/mixed-ovarian-carcinoma.html index 84a4b11c60..1e194f1946 100644 --- a/concepts/mixed-ovarian-carcinoma.html +++ b/concepts/mixed-ovarian-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mixed-phenotype-acute-leukemia-b-myeloid-nos.html b/concepts/mixed-phenotype-acute-leukemia-b-myeloid-nos.html index 3683e68709..84ab74c88f 100644 --- a/concepts/mixed-phenotype-acute-leukemia-b-myeloid-nos.html +++ b/concepts/mixed-phenotype-acute-leukemia-b-myeloid-nos.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mixed-phenotype-acute-leukemia-t-myeloid-nos.html b/concepts/mixed-phenotype-acute-leukemia-t-myeloid-nos.html index 1e0105f61a..cc74bcb9f0 100644 --- a/concepts/mixed-phenotype-acute-leukemia-t-myeloid-nos.html +++ b/concepts/mixed-phenotype-acute-leukemia-t-myeloid-nos.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mixed-phenotype-acute-leukemia-with-t922q34.1q11.2-bcr-abl1.html b/concepts/mixed-phenotype-acute-leukemia-with-t922q34.1q11.2-bcr-abl1.html index 79a1fe09a0..1490ef9e36 100644 --- a/concepts/mixed-phenotype-acute-leukemia-with-t922q34.1q11.2-bcr-abl1.html +++ b/concepts/mixed-phenotype-acute-leukemia-with-t922q34.1q11.2-bcr-abl1.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mixed-phenotype-acute-leukemia-with-tv11q23.3-kmt2a-rearranged.html b/concepts/mixed-phenotype-acute-leukemia-with-tv11q23.3-kmt2a-rearranged.html index 84d811932c..499cd2873e 100644 --- a/concepts/mixed-phenotype-acute-leukemia-with-tv11q23.3-kmt2a-rearranged.html +++ b/concepts/mixed-phenotype-acute-leukemia-with-tv11q23.3-kmt2a-rearranged.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mixed-type-metaplastic-breast-cancer.html b/concepts/mixed-type-metaplastic-breast-cancer.html index fc4205cf27..23acac0d27 100644 --- a/concepts/mixed-type-metaplastic-breast-cancer.html +++ b/concepts/mixed-type-metaplastic-breast-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mmcr-mo.html b/concepts/mmcr-mo.html index 60b2e8b78f..4b7a47d688 100644 --- a/concepts/mmcr-mo.html +++ b/concepts/mmcr-mo.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mncr-ma.html b/concepts/mncr-ma.html index b5b0243845..df76082e14 100644 --- a/concepts/mncr-ma.html +++ b/concepts/mncr-ma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/moderna.html b/concepts/moderna.html index 6e4f49cdf8..134a9a0651 100644 --- a/concepts/moderna.html +++ b/concepts/moderna.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/moffitt-cancer-center.html b/concepts/moffitt-cancer-center.html index c7f5e14297..a860f0ff2c 100644 --- a/concepts/moffitt-cancer-center.html +++ b/concepts/moffitt-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/molar-pregnancy.html b/concepts/molar-pregnancy.html index 6ae7ad1aa8..8e7aae017e 100644 --- a/concepts/molar-pregnancy.html +++ b/concepts/molar-pregnancy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/monoclonal-b-cell-lymphocytosis.html b/concepts/monoclonal-b-cell-lymphocytosis.html index b4acc2f185..a94bda81f6 100644 --- a/concepts/monoclonal-b-cell-lymphocytosis.html +++ b/concepts/monoclonal-b-cell-lymphocytosis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/monoclonal-gammopathy-of-undetermined-significance.html b/concepts/monoclonal-gammopathy-of-undetermined-significance.html index c1a701b4ef..4f8d73fcdd 100644 --- a/concepts/monoclonal-gammopathy-of-undetermined-significance.html +++ b/concepts/monoclonal-gammopathy-of-undetermined-significance.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/monoclonal-immunoglobulin-deposition-diseases-other.html b/concepts/monoclonal-immunoglobulin-deposition-diseases-other.html index 645ade745c..e71be8924b 100644 --- a/concepts/monoclonal-immunoglobulin-deposition-diseases-other.html +++ b/concepts/monoclonal-immunoglobulin-deposition-diseases-other.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/monoclonal-immunoglobulin-deposition-diseases.html b/concepts/monoclonal-immunoglobulin-deposition-diseases.html index c3ccb77ca3..8c104c095c 100644 --- a/concepts/monoclonal-immunoglobulin-deposition-diseases.html +++ b/concepts/monoclonal-immunoglobulin-deposition-diseases.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/monomorphic-epitheliotropic-intestinal-t-cell-lymphoma.html b/concepts/monomorphic-epitheliotropic-intestinal-t-cell-lymphoma.html index 6cb0789bcf..87921ed509 100644 --- a/concepts/monomorphic-epitheliotropic-intestinal-t-cell-lymphoma.html +++ b/concepts/monomorphic-epitheliotropic-intestinal-t-cell-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/monomorphic-ptld-b--and-t--nk-cell-types.html b/concepts/monomorphic-ptld-b--and-t--nk-cell-types.html index 76f333d3e9..e1c5f7bb75 100644 --- a/concepts/monomorphic-ptld-b--and-t--nk-cell-types.html +++ b/concepts/monomorphic-ptld-b--and-t--nk-cell-types.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/montefiore-einstein-cancer-center.html b/concepts/montefiore-einstein-cancer-center.html index 0852c6ad90..449b0fbb22 100644 --- a/concepts/montefiore-einstein-cancer-center.html +++ b/concepts/montefiore-einstein-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/moores-comprehensive-cancer-center.html b/concepts/moores-comprehensive-cancer-center.html index be9f9cd47c..d7d6a20b9c 100644 --- a/concepts/moores-comprehensive-cancer-center.html +++ b/concepts/moores-comprehensive-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mrisktool.html b/concepts/mrisktool.html index 81068905c1..c2a9ac9424 100644 --- a/concepts/mrisktool.html +++ b/concepts/mrisktool.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mt-sdna.html b/concepts/mt-sdna.html index f5040dbc6b..8bf8883e18 100644 --- a/concepts/mt-sdna.html +++ b/concepts/mt-sdna.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mu-heavy-chain-disease.html b/concepts/mu-heavy-chain-disease.html index 4cf00cf98c..e0f7673b71 100644 --- a/concepts/mu-heavy-chain-disease.html +++ b/concepts/mu-heavy-chain-disease.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mucinous-adenocarcinoma-of-the-appendix.html b/concepts/mucinous-adenocarcinoma-of-the-appendix.html index 91638cd599..0e0fe28aff 100644 --- a/concepts/mucinous-adenocarcinoma-of-the-appendix.html +++ b/concepts/mucinous-adenocarcinoma-of-the-appendix.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mucinous-adenocarcinoma-of-the-colon-and-rectum.html b/concepts/mucinous-adenocarcinoma-of-the-colon-and-rectum.html index 7095764fe8..b425687308 100644 --- a/concepts/mucinous-adenocarcinoma-of-the-colon-and-rectum.html +++ b/concepts/mucinous-adenocarcinoma-of-the-colon-and-rectum.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mucinous-adenocarcinoma-of-the-vulva-vagina.html b/concepts/mucinous-adenocarcinoma-of-the-vulva-vagina.html index 4115dd28bf..1535e43c4e 100644 --- a/concepts/mucinous-adenocarcinoma-of-the-vulva-vagina.html +++ b/concepts/mucinous-adenocarcinoma-of-the-vulva-vagina.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mucinous-borderline-ovarian-tumor.html b/concepts/mucinous-borderline-ovarian-tumor.html index d04d09f6cd..4d2f51a0ca 100644 --- a/concepts/mucinous-borderline-ovarian-tumor.html +++ b/concepts/mucinous-borderline-ovarian-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mucinous-carcinoma.html b/concepts/mucinous-carcinoma.html index 0c476247b4..3b47aa1014 100644 --- a/concepts/mucinous-carcinoma.html +++ b/concepts/mucinous-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mucinous-cystic-neoplasm.html b/concepts/mucinous-cystic-neoplasm.html index c1c37d2181..44b5141daa 100644 --- a/concepts/mucinous-cystic-neoplasm.html +++ b/concepts/mucinous-cystic-neoplasm.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mucinous-ovarian-cancer.html b/concepts/mucinous-ovarian-cancer.html index eb72c20bf5..5dfb621267 100644 --- a/concepts/mucinous-ovarian-cancer.html +++ b/concepts/mucinous-ovarian-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mucinous-stomach-adenocarcinoma.html b/concepts/mucinous-stomach-adenocarcinoma.html index 26e8d2529e..88cc312ddf 100644 --- a/concepts/mucinous-stomach-adenocarcinoma.html +++ b/concepts/mucinous-stomach-adenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mucoepidermoid-carcinoma-of-the-lung.html b/concepts/mucoepidermoid-carcinoma-of-the-lung.html index afb3afa6cb..593d0c9288 100644 --- a/concepts/mucoepidermoid-carcinoma-of-the-lung.html +++ b/concepts/mucoepidermoid-carcinoma-of-the-lung.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mucoepidermoid-carcinoma.html b/concepts/mucoepidermoid-carcinoma.html index d4bf7253cf..ffebfab725 100644 --- a/concepts/mucoepidermoid-carcinoma.html +++ b/concepts/mucoepidermoid-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mucosal-melanoma-of-the-esophagus.html b/concepts/mucosal-melanoma-of-the-esophagus.html index dae04d4f1d..6da2f9305e 100644 --- a/concepts/mucosal-melanoma-of-the-esophagus.html +++ b/concepts/mucosal-melanoma-of-the-esophagus.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mucosal-melanoma-of-the-urethra.html b/concepts/mucosal-melanoma-of-the-urethra.html index 52cbf92176..fcf673819e 100644 --- a/concepts/mucosal-melanoma-of-the-urethra.html +++ b/concepts/mucosal-melanoma-of-the-urethra.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mucosal-melanoma-of-the-vulva-vagina.html b/concepts/mucosal-melanoma-of-the-vulva-vagina.html index 38773907d7..6fd2d7a076 100644 --- a/concepts/mucosal-melanoma-of-the-vulva-vagina.html +++ b/concepts/mucosal-melanoma-of-the-vulva-vagina.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/multiple-myeloma.html b/concepts/multiple-myeloma.html index 67505f18c7..2f87c44ea6 100644 --- a/concepts/multiple-myeloma.html +++ b/concepts/multiple-myeloma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/multiplemyeloma-subreddit.html b/concepts/multiplemyeloma-subreddit.html index d0a42cb3d4..8c031c1593 100644 --- a/concepts/multiplemyeloma-subreddit.html +++ b/concepts/multiplemyeloma-subreddit.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/music-therapy.html b/concepts/music-therapy.html index 2ab8716800..31c85a6975 100644 --- a/concepts/music-therapy.html +++ b/concepts/music-therapy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mycanceriq.html b/concepts/mycanceriq.html index 9da671fe85..3693920319 100644 --- a/concepts/mycanceriq.html +++ b/concepts/mycanceriq.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mychart.html b/concepts/mychart.html index 2087fdcbcb..14634fa282 100644 --- a/concepts/mychart.html +++ b/concepts/mychart.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/mycosis-fungoides.html b/concepts/mycosis-fungoides.html index f5c2082756..3c9a211798 100644 --- a/concepts/mycosis-fungoides.html +++ b/concepts/mycosis-fungoides.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/myelodysplastic-myeloproliferative-neoplasms.html b/concepts/myelodysplastic-myeloproliferative-neoplasms.html index 8f5b7b4b62..2350f192a4 100644 --- a/concepts/myelodysplastic-myeloproliferative-neoplasms.html +++ b/concepts/myelodysplastic-myeloproliferative-neoplasms.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/myelodysplastic-syndromes.html b/concepts/myelodysplastic-syndromes.html index c45719e45d..fb7e2bb42f 100644 --- a/concepts/myelodysplastic-syndromes.html +++ b/concepts/myelodysplastic-syndromes.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/myeloid-atypical.html b/concepts/myeloid-atypical.html index b08a26f912..b7009d941a 100644 --- a/concepts/myeloid-atypical.html +++ b/concepts/myeloid-atypical.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/myeloid-benign.html b/concepts/myeloid-benign.html index 4c18179395..3916d85d76 100644 --- a/concepts/myeloid-benign.html +++ b/concepts/myeloid-benign.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/myeloid-leukemia-associated-with-down-syndrome.html b/concepts/myeloid-leukemia-associated-with-down-syndrome.html index 8fd6da146a..62a29b4db0 100644 --- a/concepts/myeloid-leukemia-associated-with-down-syndrome.html +++ b/concepts/myeloid-leukemia-associated-with-down-syndrome.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/myeloid-lymphoid-neoplasms-with-eosinophilia-and-rearrangement-of-pdgfra-pdgfrb-or-fgfr1-or-with-pcm1-jak2.html b/concepts/myeloid-lymphoid-neoplasms-with-eosinophilia-and-rearrangement-of-pdgfra-pdgfrb-or-fgfr1-or-with-pcm1-jak2.html index 6bc2ac6815..6a81c623c2 100644 --- a/concepts/myeloid-lymphoid-neoplasms-with-eosinophilia-and-rearrangement-of-pdgfra-pdgfrb-or-fgfr1-or-with-pcm1-jak2.html +++ b/concepts/myeloid-lymphoid-neoplasms-with-eosinophilia-and-rearrangement-of-pdgfra-pdgfrb-or-fgfr1-or-with-pcm1-jak2.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/myeloid-lymphoid-neoplasms-with-fgfr1-rearrangement.html b/concepts/myeloid-lymphoid-neoplasms-with-fgfr1-rearrangement.html index a0ecd38470..9a049a7469 100644 --- a/concepts/myeloid-lymphoid-neoplasms-with-fgfr1-rearrangement.html +++ b/concepts/myeloid-lymphoid-neoplasms-with-fgfr1-rearrangement.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/myeloid-lymphoid-neoplasms-with-pcm1-jak2.html b/concepts/myeloid-lymphoid-neoplasms-with-pcm1-jak2.html index ae2ac82964..0b79d2888e 100644 --- a/concepts/myeloid-lymphoid-neoplasms-with-pcm1-jak2.html +++ b/concepts/myeloid-lymphoid-neoplasms-with-pcm1-jak2.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/myeloid-lymphoid-neoplasms-with-pdgfra-rearrangement.html b/concepts/myeloid-lymphoid-neoplasms-with-pdgfra-rearrangement.html index f05ecb3b21..71e74c8fc4 100644 --- a/concepts/myeloid-lymphoid-neoplasms-with-pdgfra-rearrangement.html +++ b/concepts/myeloid-lymphoid-neoplasms-with-pdgfra-rearrangement.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/myeloid-lymphoid-neoplasms-with-pdgfrb-rearrangement.html b/concepts/myeloid-lymphoid-neoplasms-with-pdgfrb-rearrangement.html index b0fb6d1077..ffb86b3208 100644 --- a/concepts/myeloid-lymphoid-neoplasms-with-pdgfrb-rearrangement.html +++ b/concepts/myeloid-lymphoid-neoplasms-with-pdgfrb-rearrangement.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/myeloid-neoplasm.html b/concepts/myeloid-neoplasm.html index 8c25638cfc..4450935c24 100644 --- a/concepts/myeloid-neoplasm.html +++ b/concepts/myeloid-neoplasm.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/myeloid-neoplasms-with-germ-line-predisposition.html b/concepts/myeloid-neoplasms-with-germ-line-predisposition.html index 6c96358287..c9e90556ff 100644 --- a/concepts/myeloid-neoplasms-with-germ-line-predisposition.html +++ b/concepts/myeloid-neoplasms-with-germ-line-predisposition.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/myeloid-proliferations-related-to-down-syndrome.html b/concepts/myeloid-proliferations-related-to-down-syndrome.html index 1d0f773a4f..b76d27b653 100644 --- a/concepts/myeloid-proliferations-related-to-down-syndrome.html +++ b/concepts/myeloid-proliferations-related-to-down-syndrome.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/myeloid-sarcoma.html b/concepts/myeloid-sarcoma.html index 81a2afaabe..3227200216 100644 --- a/concepts/myeloid-sarcoma.html +++ b/concepts/myeloid-sarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/myeloid.html b/concepts/myeloid.html index f6ad84ac57..89f7a9b4cc 100644 --- a/concepts/myeloid.html +++ b/concepts/myeloid.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/myeloproliferative-neoplasms-unclassifiable.html b/concepts/myeloproliferative-neoplasms-unclassifiable.html index 0a0fdf2a53..47c1c308d4 100644 --- a/concepts/myeloproliferative-neoplasms-unclassifiable.html +++ b/concepts/myeloproliferative-neoplasms-unclassifiable.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/myeloproliferative-neoplasms.html b/concepts/myeloproliferative-neoplasms.html index 3b74283836..45dc1fa6c2 100644 --- a/concepts/myeloproliferative-neoplasms.html +++ b/concepts/myeloproliferative-neoplasms.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/myoepithelial-carcinoma.html b/concepts/myoepithelial-carcinoma.html index 6f5f4da28e..c9fdf28a7b 100644 --- a/concepts/myoepithelial-carcinoma.html +++ b/concepts/myoepithelial-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/myofibroma.html b/concepts/myofibroma.html index be2c88d839..a9ca5683e1 100644 --- a/concepts/myofibroma.html +++ b/concepts/myofibroma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/myofibromatosis.html b/concepts/myofibromatosis.html index e9c72e62c1..d6f32e4911 100644 --- a/concepts/myofibromatosis.html +++ b/concepts/myofibromatosis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/myopericytoma.html b/concepts/myopericytoma.html index 6ed6af42f1..7213cbd10e 100644 --- a/concepts/myopericytoma.html +++ b/concepts/myopericytoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/myxofibrosarcoma.html b/concepts/myxofibrosarcoma.html index cdf542a591..9fce7d6337 100644 --- a/concepts/myxofibrosarcoma.html +++ b/concepts/myxofibrosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/myxoid-chondrosarcoma.html b/concepts/myxoid-chondrosarcoma.html index 2495a25b4e..ce4c436228 100644 --- a/concepts/myxoid-chondrosarcoma.html +++ b/concepts/myxoid-chondrosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/myxoid-round-cell-liposarcoma.html b/concepts/myxoid-round-cell-liposarcoma.html index 0f97702be3..393100bf0a 100644 --- a/concepts/myxoid-round-cell-liposarcoma.html +++ b/concepts/myxoid-round-cell-liposarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/myxoma.html b/concepts/myxoma.html index 361fdcaccd..98298fc50d 100644 --- a/concepts/myxoma.html +++ b/concepts/myxoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/myxopapillary-ependymoma.html b/concepts/myxopapillary-ependymoma.html index 38f53a7a91..71d581870c 100644 --- a/concepts/myxopapillary-ependymoma.html +++ b/concepts/myxopapillary-ependymoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/naaccr.html b/concepts/naaccr.html index 559ee4aa8e..6f58859c6d 100644 --- a/concepts/naaccr.html +++ b/concepts/naaccr.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nacr-ng.html b/concepts/nacr-ng.html index 44f3cda132..9bd71bfc30 100644 --- a/concepts/nacr-ng.html +++ b/concepts/nacr-ng.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nasopharyngeal-carcinoma.html b/concepts/nasopharyngeal-carcinoma.html index 53163081b1..34d2acaacb 100644 --- a/concepts/nasopharyngeal-carcinoma.html +++ b/concepts/nasopharyngeal-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/national-breast-cancer-foundation.html b/concepts/national-breast-cancer-foundation.html index 072975c198..1b697d0ba6 100644 --- a/concepts/national-breast-cancer-foundation.html +++ b/concepts/national-breast-cancer-foundation.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/national-cancer-center-japan.html b/concepts/national-cancer-center-japan.html index 33feb2a9f9..5b31b13509 100644 --- a/concepts/national-cancer-center-japan.html +++ b/concepts/national-cancer-center-japan.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/national-cancer-center-korea.html b/concepts/national-cancer-center-korea.html index 3470d7a9ce..f6021a7d25 100644 --- a/concepts/national-cancer-center-korea.html +++ b/concepts/national-cancer-center-korea.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/national-cancer-grid-india.html b/concepts/national-cancer-grid-india.html index c6d9bf697c..5b100b03db 100644 --- a/concepts/national-cancer-grid-india.html +++ b/concepts/national-cancer-grid-india.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/national-cancer-institute-brazil.html b/concepts/national-cancer-institute-brazil.html index 33446c1ebc..76f9f5b88b 100644 --- a/concepts/national-cancer-institute-brazil.html +++ b/concepts/national-cancer-institute-brazil.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/national-cancer-research-institute-uk.html b/concepts/national-cancer-research-institute-uk.html index d7997e58aa..2500fe05ee 100644 --- a/concepts/national-cancer-research-institute-uk.html +++ b/concepts/national-cancer-research-institute-uk.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/national-prostate-cancer-awareness-month.html b/concepts/national-prostate-cancer-awareness-month.html index bf54244af1..1a5705e0c4 100644 --- a/concepts/national-prostate-cancer-awareness-month.html +++ b/concepts/national-prostate-cancer-awareness-month.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/natural-killer-nk-cell-lymphoblastic-leukemia-lymphoma.html b/concepts/natural-killer-nk-cell-lymphoblastic-leukemia-lymphoma.html index 82cb155545..086076057d 100644 --- a/concepts/natural-killer-nk-cell-lymphoblastic-leukemia-lymphoma.html +++ b/concepts/natural-killer-nk-cell-lymphoblastic-leukemia-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/natural-strategies-for-cancer-patients.html b/concepts/natural-strategies-for-cancer-patients.html index ece3cd4839..896c0f18e6 100644 --- a/concepts/natural-strategies-for-cancer-patients.html +++ b/concepts/natural-strategies-for-cancer-patients.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nature-reviews-cancer.html b/concepts/nature-reviews-cancer.html index 5c93cc49fe..9685fb4864 100644 --- a/concepts/nature-reviews-cancer.html +++ b/concepts/nature-reviews-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/navigational-bronchoscopy.html b/concepts/navigational-bronchoscopy.html index 52ae05f7f3..d2d6f4bf2e 100644 --- a/concepts/navigational-bronchoscopy.html +++ b/concepts/navigational-bronchoscopy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ncbi-gene.html b/concepts/ncbi-gene.html index 5f333aa407..7d449b4812 100644 --- a/concepts/ncbi-gene.html +++ b/concepts/ncbi-gene.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ncbi.html b/concepts/ncbi.html index 3b732c4f64..6486fb325c 100644 --- a/concepts/ncbi.html +++ b/concepts/ncbi.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ncc.html b/concepts/ncc.html index a15a025994..95fb7e546d 100644 --- a/concepts/ncc.html +++ b/concepts/ncc.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nccn.html b/concepts/nccn.html index 406ae2fa85..971c398820 100644 --- a/concepts/nccn.html +++ b/concepts/nccn.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nccp-af.html b/concepts/nccp-af.html index fb499b2185..2b516d5a26 100644 --- a/concepts/nccp-af.html +++ b/concepts/nccp-af.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nccp-al.html b/concepts/nccp-al.html index c31bd454d1..8310c2a624 100644 --- a/concepts/nccp-al.html +++ b/concepts/nccp-al.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nccr.html b/concepts/nccr.html index 3b4c1e3c02..5292825217 100644 --- a/concepts/nccr.html +++ b/concepts/nccr.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nccs.html b/concepts/nccs.html index e0257c5279..778a026f74 100644 --- a/concepts/nccs.html +++ b/concepts/nccs.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ncdb.html b/concepts/ncdb.html index 1821f0b158..5388d35b7e 100644 --- a/concepts/ncdb.html +++ b/concepts/ncdb.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nci.html b/concepts/nci.html index 0a4e19cb12..af93e45d2e 100644 --- a/concepts/nci.html +++ b/concepts/nci.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ncr-ar.html b/concepts/ncr-ar.html index d356cc80aa..ec24d7f9f7 100644 --- a/concepts/ncr-ar.html +++ b/concepts/ncr-ar.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ncra.html b/concepts/ncra.html index 86d11d291b..f770047c33 100644 --- a/concepts/ncra.html +++ b/concepts/ncra.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ncrsa-za.html b/concepts/ncrsa-za.html index b2c2b020c3..f08c3006b7 100644 --- a/concepts/ncrsa-za.html +++ b/concepts/ncrsa-za.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nedaplatin.html b/concepts/nedaplatin.html index 41f975fd2b..6e619003e6 100644 --- a/concepts/nedaplatin.html +++ b/concepts/nedaplatin.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nephrectomy.html b/concepts/nephrectomy.html index bbdf40656a..1500440752 100644 --- a/concepts/nephrectomy.html +++ b/concepts/nephrectomy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nerve-sheath-tumor.html b/concepts/nerve-sheath-tumor.html index 3dc638c5c7..824112635e 100644 --- a/concepts/nerve-sheath-tumor.html +++ b/concepts/nerve-sheath-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/netherlands-ncc.html b/concepts/netherlands-ncc.html index bf6ea628f7..55cbb6333a 100644 --- a/concepts/netherlands-ncc.html +++ b/concepts/netherlands-ncc.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/neuroblastoma.html b/concepts/neuroblastoma.html index 50931a5dcd..f65f416304 100644 --- a/concepts/neuroblastoma.html +++ b/concepts/neuroblastoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/neuroendocrine-carcinoma-nos.html b/concepts/neuroendocrine-carcinoma-nos.html index b28d2094c2..a0409966bd 100644 --- a/concepts/neuroendocrine-carcinoma-nos.html +++ b/concepts/neuroendocrine-carcinoma-nos.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/neuroendocrine-tumor-nos.html b/concepts/neuroendocrine-tumor-nos.html index 00763aca1d..4c7300b2fd 100644 --- a/concepts/neuroendocrine-tumor-nos.html +++ b/concepts/neuroendocrine-tumor-nos.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/neurofibroma.html b/concepts/neurofibroma.html index fe02b10228..be7c10c803 100644 --- a/concepts/neurofibroma.html +++ b/concepts/neurofibroma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/newyork-presbyterian-hospital.html b/concepts/newyork-presbyterian-hospital.html index 639d6075fd..ffd8f937c9 100644 --- a/concepts/newyork-presbyterian-hospital.html +++ b/concepts/newyork-presbyterian-hospital.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nfr.html b/concepts/nfr.html index dca13c4b9d..9f7986735e 100644 --- a/concepts/nfr.html +++ b/concepts/nfr.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nhgri.html b/concepts/nhgri.html index 267665b71b..291e46e687 100644 --- a/concepts/nhgri.html +++ b/concepts/nhgri.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nicr.html b/concepts/nicr.html index e90bef78fa..926d75b88d 100644 --- a/concepts/nicr.html +++ b/concepts/nicr.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nih.html b/concepts/nih.html index 9503fd2220..27bfaa732d 100644 --- a/concepts/nih.html +++ b/concepts/nih.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nimustine.html b/concepts/nimustine.html index 63ff70fd80..bc6d9c4ea0 100644 --- a/concepts/nimustine.html +++ b/concepts/nimustine.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nitrosoureas.html b/concepts/nitrosoureas.html index 9c16694aed..2b1c07b95b 100644 --- a/concepts/nitrosoureas.html +++ b/concepts/nitrosoureas.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nivolumab.html b/concepts/nivolumab.html index 0d637b6f97..39cf276b79 100644 --- a/concepts/nivolumab.html +++ b/concepts/nivolumab.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nlm.html b/concepts/nlm.html index 11758f929d..62b13342ff 100644 --- a/concepts/nlm.html +++ b/concepts/nlm.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nncr-na.html b/concepts/nncr-na.html index a9e2b95a90..dc996f89f3 100644 --- a/concepts/nncr-na.html +++ b/concepts/nncr-na.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nodal-marginal-zone-lymphoma.html b/concepts/nodal-marginal-zone-lymphoma.html index e03e57dff9..6d77323638 100644 --- a/concepts/nodal-marginal-zone-lymphoma.html +++ b/concepts/nodal-marginal-zone-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nodal-peripheral-t-cell-lymphoma-with-tfh-phenotype.html b/concepts/nodal-peripheral-t-cell-lymphoma-with-tfh-phenotype.html index e162ba61bb..2616274d43 100644 --- a/concepts/nodal-peripheral-t-cell-lymphoma-with-tfh-phenotype.html +++ b/concepts/nodal-peripheral-t-cell-lymphoma-with-tfh-phenotype.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nodular-lymphocyte-predominant-hodgkin-lymphoma.html b/concepts/nodular-lymphocyte-predominant-hodgkin-lymphoma.html index e2cac28b6d..abb9ee5310 100644 --- a/concepts/nodular-lymphocyte-predominant-hodgkin-lymphoma.html +++ b/concepts/nodular-lymphocyte-predominant-hodgkin-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nodular-sclerosis-classical-hodgkin-lymphoma.html b/concepts/nodular-sclerosis-classical-hodgkin-lymphoma.html index 2daa418437..d20c18a093 100644 --- a/concepts/nodular-sclerosis-classical-hodgkin-lymphoma.html +++ b/concepts/nodular-sclerosis-classical-hodgkin-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/non-hodgkin-lymphoma.html b/concepts/non-hodgkin-lymphoma.html index 68048df4ab..cd0b7d1efc 100644 --- a/concepts/non-hodgkin-lymphoma.html +++ b/concepts/non-hodgkin-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/non-seminomatous-germ-cell-tumor.html b/concepts/non-seminomatous-germ-cell-tumor.html index fefa9ce050..2c1bd86f77 100644 --- a/concepts/non-seminomatous-germ-cell-tumor.html +++ b/concepts/non-seminomatous-germ-cell-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/non-small-cell-lung-cancer.html b/concepts/non-small-cell-lung-cancer.html index 99a6b41767..bf9e60d242 100644 --- a/concepts/non-small-cell-lung-cancer.html +++ b/concepts/non-small-cell-lung-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/norwegian-cancer-society.html b/concepts/norwegian-cancer-society.html index 1e97aa24a6..0361ae25e1 100644 --- a/concepts/norwegian-cancer-society.html +++ b/concepts/norwegian-cancer-society.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/novartis.html b/concepts/novartis.html index 0362311875..29bd9c00b8 100644 --- a/concepts/novartis.html +++ b/concepts/novartis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/novo-nordisk.html b/concepts/novo-nordisk.html index c95f981e8f..4cdabb68e0 100644 --- a/concepts/novo-nordisk.html +++ b/concepts/novo-nordisk.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nscr-ng.html b/concepts/nscr-ng.html index 9decdbf671..a050f1700d 100644 --- a/concepts/nscr-ng.html +++ b/concepts/nscr-ng.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nswcr-au.html b/concepts/nswcr-au.html index f8279b674a..1cee360da6 100644 --- a/concepts/nswcr-au.html +++ b/concepts/nswcr-au.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ntcr-au.html b/concepts/ntcr-au.html index 15263d8389..6a5b03ffd2 100644 --- a/concepts/ntcr-au.html +++ b/concepts/ntcr-au.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nut-carcinoma-of-the-lung.html b/concepts/nut-carcinoma-of-the-lung.html index 1d41b2af94..1cc6a9a19e 100644 --- a/concepts/nut-carcinoma-of-the-lung.html +++ b/concepts/nut-carcinoma-of-the-lung.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nut-midline-carcinoma-of-the-head-and-neck.html b/concepts/nut-midline-carcinoma-of-the-head-and-neck.html index 7f30a64d40..fc731b8418 100644 --- a/concepts/nut-midline-carcinoma-of-the-head-and-neck.html +++ b/concepts/nut-midline-carcinoma-of-the-head-and-neck.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/nutrition.html b/concepts/nutrition.html index b56ece39ef..c47c87a9ad 100644 --- a/concepts/nutrition.html +++ b/concepts/nutrition.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ocular-melanoma.html b/concepts/ocular-melanoma.html index a82006d786..ebc60fc004 100644 --- a/concepts/ocular-melanoma.html +++ b/concepts/ocular-melanoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/odontogenic-carcinoma.html b/concepts/odontogenic-carcinoma.html index 21d6bf986b..a5dc4a8964 100644 --- a/concepts/odontogenic-carcinoma.html +++ b/concepts/odontogenic-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/olfactory-neuroblastoma.html b/concepts/olfactory-neuroblastoma.html index 6c1a3bb368..19a93d3c44 100644 --- a/concepts/olfactory-neuroblastoma.html +++ b/concepts/olfactory-neuroblastoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/oligoastrocytoma.html b/concepts/oligoastrocytoma.html index cbe8bc29a3..27013a94ff 100644 --- a/concepts/oligoastrocytoma.html +++ b/concepts/oligoastrocytoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/oligodendroglioma.html b/concepts/oligodendroglioma.html index e762a1c84a..2cb071e33d 100644 --- a/concepts/oligodendroglioma.html +++ b/concepts/oligodendroglioma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/onclive.html b/concepts/onclive.html index 1a9246cb2b..81385f1c18 100644 --- a/concepts/onclive.html +++ b/concepts/onclive.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/oncocytic-adenoma-of-the-thyroid.html b/concepts/oncocytic-adenoma-of-the-thyroid.html index 5614f290ee..d9311395ee 100644 --- a/concepts/oncocytic-adenoma-of-the-thyroid.html +++ b/concepts/oncocytic-adenoma-of-the-thyroid.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/oncokb.html b/concepts/oncokb.html index db0d397474..79ce7c1946 100644 --- a/concepts/oncokb.html +++ b/concepts/oncokb.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/oncotree.html b/concepts/oncotree.html index 130fab2dbe..ea54e1c00a 100644 --- a/concepts/oncotree.html +++ b/concepts/oncotree.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/one-plus-one-cars.html b/concepts/one-plus-one-cars.html index 518580fb28..e7418f51e1 100644 --- a/concepts/one-plus-one-cars.html +++ b/concepts/one-plus-one-cars.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/oneal-comprehensive-cancer-center.html b/concepts/oneal-comprehensive-cancer-center.html index 358b0376ed..733d56f668 100644 --- a/concepts/oneal-comprehensive-cancer-center.html +++ b/concepts/oneal-comprehensive-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/oophorectomy.html b/concepts/oophorectomy.html index 4239a02176..19f0175138 100644 --- a/concepts/oophorectomy.html +++ b/concepts/oophorectomy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/oral-administration.html b/concepts/oral-administration.html index fdeaab940e..780133ed57 100644 --- a/concepts/oral-administration.html +++ b/concepts/oral-administration.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/oral-cancer-foundation.html b/concepts/oral-cancer-foundation.html index 31a2c815ed..52b702412e 100644 --- a/concepts/oral-cancer-foundation.html +++ b/concepts/oral-cancer-foundation.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/oral-cancer.html b/concepts/oral-cancer.html index cc2e80a20f..c05e8cd21e 100644 --- a/concepts/oral-cancer.html +++ b/concepts/oral-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/oral-cavity-squamous-cell-carcinoma.html b/concepts/oral-cavity-squamous-cell-carcinoma.html index 89bdb22906..b727aea594 100644 --- a/concepts/oral-cavity-squamous-cell-carcinoma.html +++ b/concepts/oral-cavity-squamous-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/orchiectomy.html b/concepts/orchiectomy.html index be356e56e0..fdf927b4d9 100644 --- a/concepts/orchiectomy.html +++ b/concepts/orchiectomy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/oropharyngeal-cancer.html b/concepts/oropharyngeal-cancer.html index 30c3f7e031..fefaf1c9c5 100644 --- a/concepts/oropharyngeal-cancer.html +++ b/concepts/oropharyngeal-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/oropharynx-squamous-cell-carcinoma.html b/concepts/oropharynx-squamous-cell-carcinoma.html index 90408fdb7a..4f880cfa0e 100644 --- a/concepts/oropharynx-squamous-cell-carcinoma.html +++ b/concepts/oropharynx-squamous-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ossifying-fibromyxoid-tumor.html b/concepts/ossifying-fibromyxoid-tumor.html index eb6669fabd..43f80e2ec8 100644 --- a/concepts/ossifying-fibromyxoid-tumor.html +++ b/concepts/ossifying-fibromyxoid-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/osteoblastic-osteosarcoma.html b/concepts/osteoblastic-osteosarcoma.html index d0fd350481..6e98bf7699 100644 --- a/concepts/osteoblastic-osteosarcoma.html +++ b/concepts/osteoblastic-osteosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/osteoclastic-giant-cell-tumor.html b/concepts/osteoclastic-giant-cell-tumor.html index 7285411622..092e1537d9 100644 --- a/concepts/osteoclastic-giant-cell-tumor.html +++ b/concepts/osteoclastic-giant-cell-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/osteosarcoma.html b/concepts/osteosarcoma.html index c3046b05c2..7bfe27d526 100644 --- a/concepts/osteosarcoma.html +++ b/concepts/osteosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/other-uterine-tumor.html b/concepts/other-uterine-tumor.html index 1dfe5558a6..37add042bf 100644 --- a/concepts/other-uterine-tumor.html +++ b/concepts/other-uterine-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/other.html b/concepts/other.html index 54474ae272..76800379a7 100644 --- a/concepts/other.html +++ b/concepts/other.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/our-world-in-data-cancer.html b/concepts/our-world-in-data-cancer.html index 9ccc0fc676..ca29b4cc5e 100644 --- a/concepts/our-world-in-data-cancer.html +++ b/concepts/our-world-in-data-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ovarian-cancer-other.html b/concepts/ovarian-cancer-other.html index 037ae24033..16d9c70f0e 100644 --- a/concepts/ovarian-cancer-other.html +++ b/concepts/ovarian-cancer-other.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ovarian-cancer-subreddit.html b/concepts/ovarian-cancer-subreddit.html index 036d77b1ab..37b4702137 100644 --- a/concepts/ovarian-cancer-subreddit.html +++ b/concepts/ovarian-cancer-subreddit.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ovarian-cancer.html b/concepts/ovarian-cancer.html index 514eb487ab..7dd47886a4 100644 --- a/concepts/ovarian-cancer.html +++ b/concepts/ovarian-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ovarian-carcinosarcoma-malignant-mixed-mesodermal-tumor.html b/concepts/ovarian-carcinosarcoma-malignant-mixed-mesodermal-tumor.html index 020172e007..40b59d50aa 100644 --- a/concepts/ovarian-carcinosarcoma-malignant-mixed-mesodermal-tumor.html +++ b/concepts/ovarian-carcinosarcoma-malignant-mixed-mesodermal-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ovarian-choriocarcinoma-nos.html b/concepts/ovarian-choriocarcinoma-nos.html index 29a20ed7c0..c494f866b9 100644 --- a/concepts/ovarian-choriocarcinoma-nos.html +++ b/concepts/ovarian-choriocarcinoma-nos.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ovarian-epithelial-tumor.html b/concepts/ovarian-epithelial-tumor.html index f87ae10b3a..1c9c0bc016 100644 --- a/concepts/ovarian-epithelial-tumor.html +++ b/concepts/ovarian-epithelial-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ovarian-germ-cell-tumor.html b/concepts/ovarian-germ-cell-tumor.html index 9c807286b7..5c9c9190b1 100644 --- a/concepts/ovarian-germ-cell-tumor.html +++ b/concepts/ovarian-germ-cell-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ovarian-seromucinous-adenoma.html b/concepts/ovarian-seromucinous-adenoma.html index f6479ef88b..45efb99795 100644 --- a/concepts/ovarian-seromucinous-adenoma.html +++ b/concepts/ovarian-seromucinous-adenoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ovarian-seromucinous-borderline-tumor.html b/concepts/ovarian-seromucinous-borderline-tumor.html index 120a0fd624..c62fec4c2e 100644 --- a/concepts/ovarian-seromucinous-borderline-tumor.html +++ b/concepts/ovarian-seromucinous-borderline-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ovarian-seromucinous-carcinoma.html b/concepts/ovarian-seromucinous-carcinoma.html index 43a41d50e9..03c0d7bc32 100644 --- a/concepts/ovarian-seromucinous-carcinoma.html +++ b/concepts/ovarian-seromucinous-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/oxaliplatin.html b/concepts/oxaliplatin.html index 4c49d32ace..e3980340ea 100644 --- a/concepts/oxaliplatin.html +++ b/concepts/oxaliplatin.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/paclitaxel.html b/concepts/paclitaxel.html index 4ff74f6d61..98050ba858 100644 --- a/concepts/paclitaxel.html +++ b/concepts/paclitaxel.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pact.html b/concepts/pact.html index 4f518e6614..beaed408c6 100644 --- a/concepts/pact.html +++ b/concepts/pact.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/paget-disease-of-the-nipple.html b/concepts/paget-disease-of-the-nipple.html index 01a6bece2a..58d85ccd66 100644 --- a/concepts/paget-disease-of-the-nipple.html +++ b/concepts/paget-disease-of-the-nipple.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/paleo-diet.html b/concepts/paleo-diet.html index a6d0f28daa..8ff8ef88c8 100644 --- a/concepts/paleo-diet.html +++ b/concepts/paleo-diet.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/panama-ncc.html b/concepts/panama-ncc.html index f3ab4d27d6..13e31ef6e8 100644 --- a/concepts/panama-ncc.html +++ b/concepts/panama-ncc.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pancreas.html b/concepts/pancreas.html index fe9015f175..6666e74d5f 100644 --- a/concepts/pancreas.html +++ b/concepts/pancreas.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pancreatectomy.html b/concepts/pancreatectomy.html index 3d920e2629..be0e4b3c3d 100644 --- a/concepts/pancreatectomy.html +++ b/concepts/pancreatectomy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pancreatic-adenocarcinoma.html b/concepts/pancreatic-adenocarcinoma.html index 05ca2753a5..8332f458d7 100644 --- a/concepts/pancreatic-adenocarcinoma.html +++ b/concepts/pancreatic-adenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pancreatic-neuroendocrine-tumor.html b/concepts/pancreatic-neuroendocrine-tumor.html index 821319d78d..fd6ebec6e9 100644 --- a/concepts/pancreatic-neuroendocrine-tumor.html +++ b/concepts/pancreatic-neuroendocrine-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pancreatobiliary-ampullary-carcinoma.html b/concepts/pancreatobiliary-ampullary-carcinoma.html index 3e080704ed..f753e1ddfa 100644 --- a/concepts/pancreatobiliary-ampullary-carcinoma.html +++ b/concepts/pancreatobiliary-ampullary-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pancreatoblastoma.html b/concepts/pancreatoblastoma.html index 1c2246bd74..ada540886b 100644 --- a/concepts/pancreatoblastoma.html +++ b/concepts/pancreatoblastoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/papillary-glioneuronal-tumor.html b/concepts/papillary-glioneuronal-tumor.html index 31c5a0a628..52f6f5acf9 100644 --- a/concepts/papillary-glioneuronal-tumor.html +++ b/concepts/papillary-glioneuronal-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/papillary-meningioma.html b/concepts/papillary-meningioma.html index 156313edb9..a2bea9892b 100644 --- a/concepts/papillary-meningioma.html +++ b/concepts/papillary-meningioma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/papillary-renal-cell-carcinoma.html b/concepts/papillary-renal-cell-carcinoma.html index a9c284d7f3..7929d3b060 100644 --- a/concepts/papillary-renal-cell-carcinoma.html +++ b/concepts/papillary-renal-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/papillary-stomach-adenocarcinoma.html b/concepts/papillary-stomach-adenocarcinoma.html index e763acc9bd..a090f4c192 100644 --- a/concepts/papillary-stomach-adenocarcinoma.html +++ b/concepts/papillary-stomach-adenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/papillary-thyroid-cancer.html b/concepts/papillary-thyroid-cancer.html index 186451caca..f4a8212bf5 100644 --- a/concepts/papillary-thyroid-cancer.html +++ b/concepts/papillary-thyroid-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/papillary-tumor-of-the-pineal-region.html b/concepts/papillary-tumor-of-the-pineal-region.html index 75a4d7e036..4877747534 100644 --- a/concepts/papillary-tumor-of-the-pineal-region.html +++ b/concepts/papillary-tumor-of-the-pineal-region.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/paraganglioma.html b/concepts/paraganglioma.html index a5cc4ab4e9..65af185a2e 100644 --- a/concepts/paraganglioma.html +++ b/concepts/paraganglioma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/parathyroid-cancer.html b/concepts/parathyroid-cancer.html index d66c2397ac..834a75c6a7 100644 --- a/concepts/parathyroid-cancer.html +++ b/concepts/parathyroid-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/parathyroid-carcinoma.html b/concepts/parathyroid-carcinoma.html index bd2c9a9bec..08bb574f07 100644 --- a/concepts/parathyroid-carcinoma.html +++ b/concepts/parathyroid-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/parosteal-osteosarcoma.html b/concepts/parosteal-osteosarcoma.html index 8e3ea7c0f8..adecff8431 100644 --- a/concepts/parosteal-osteosarcoma.html +++ b/concepts/parosteal-osteosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/partial-hydatidiform-mole.html b/concepts/partial-hydatidiform-mole.html index bcc9663af0..0457b1d60c 100644 --- a/concepts/partial-hydatidiform-mole.html +++ b/concepts/partial-hydatidiform-mole.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pediatric-type-follicular-lymphoma.html b/concepts/pediatric-type-follicular-lymphoma.html index 0fb4133498..9dae258396 100644 --- a/concepts/pediatric-type-follicular-lymphoma.html +++ b/concepts/pediatric-type-follicular-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pembrolizumab.html b/concepts/pembrolizumab.html index eaa0503955..578b486668 100644 --- a/concepts/pembrolizumab.html +++ b/concepts/pembrolizumab.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/penile-squamous-cell-carcinoma.html b/concepts/penile-squamous-cell-carcinoma.html index e08b1a70f9..7c7729c751 100644 --- a/concepts/penile-squamous-cell-carcinoma.html +++ b/concepts/penile-squamous-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/penis.html b/concepts/penis.html index 3cdb747fb7..32c25bb259 100644 --- a/concepts/penis.html +++ b/concepts/penis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/perihilar-cholangiocarcinoma.html b/concepts/perihilar-cholangiocarcinoma.html index b101920006..42a804b69d 100644 --- a/concepts/perihilar-cholangiocarcinoma.html +++ b/concepts/perihilar-cholangiocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/periosteal-osteosarcoma.html b/concepts/periosteal-osteosarcoma.html index a4ffaaa4b9..c7a8c9004f 100644 --- a/concepts/periosteal-osteosarcoma.html +++ b/concepts/periosteal-osteosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/peripheral-nervous-system.html b/concepts/peripheral-nervous-system.html index a9e4367b28..50e9e24c2d 100644 --- a/concepts/peripheral-nervous-system.html +++ b/concepts/peripheral-nervous-system.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/peripheral-t-cell-lymphoma-nos.html b/concepts/peripheral-t-cell-lymphoma-nos.html index 9c26abe1f4..c52fe9877a 100644 --- a/concepts/peripheral-t-cell-lymphoma-nos.html +++ b/concepts/peripheral-t-cell-lymphoma-nos.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/peritoneal-mesothelioma.html b/concepts/peritoneal-mesothelioma.html index c8fc957471..9a17462a84 100644 --- a/concepts/peritoneal-mesothelioma.html +++ b/concepts/peritoneal-mesothelioma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/peritoneal-serous-carcinoma.html b/concepts/peritoneal-serous-carcinoma.html index e0d039a9da..ecb8492cd7 100644 --- a/concepts/peritoneal-serous-carcinoma.html +++ b/concepts/peritoneal-serous-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/peritoneum.html b/concepts/peritoneum.html index 256402ef10..1baf8ec34f 100644 --- a/concepts/peritoneum.html +++ b/concepts/peritoneum.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/perivascular-epithelioid-cell-tumor.html b/concepts/perivascular-epithelioid-cell-tumor.html index 26755c8a97..2c6cc14d25 100644 --- a/concepts/perivascular-epithelioid-cell-tumor.html +++ b/concepts/perivascular-epithelioid-cell-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pfizer.html b/concepts/pfizer.html index 89079ae87c..c5a1aa790b 100644 --- a/concepts/pfizer.html +++ b/concepts/pfizer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/phac.html b/concepts/phac.html index 03f3730d2b..2b0bf6c159 100644 --- a/concepts/phac.html +++ b/concepts/phac.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pheochromocytoma.html b/concepts/pheochromocytoma.html index 8a22df32cc..0134387d68 100644 --- a/concepts/pheochromocytoma.html +++ b/concepts/pheochromocytoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/phyllodes-tumor-of-the-breast.html b/concepts/phyllodes-tumor-of-the-breast.html index 7098b30517..6c7740e66c 100644 --- a/concepts/phyllodes-tumor-of-the-breast.html +++ b/concepts/phyllodes-tumor-of-the-breast.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pilocytic-astrocytoma.html b/concepts/pilocytic-astrocytoma.html index 3ddf73e62a..e0c8c5ae02 100644 --- a/concepts/pilocytic-astrocytoma.html +++ b/concepts/pilocytic-astrocytoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pilomyxoid-astrocytoma.html b/concepts/pilomyxoid-astrocytoma.html index 333b8c2c61..31cc1dc44d 100644 --- a/concepts/pilomyxoid-astrocytoma.html +++ b/concepts/pilomyxoid-astrocytoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pineal-parenchymal-tumor-of-intermediate-differentiation.html b/concepts/pineal-parenchymal-tumor-of-intermediate-differentiation.html index 0a2d607bc4..7b183e00aa 100644 --- a/concepts/pineal-parenchymal-tumor-of-intermediate-differentiation.html +++ b/concepts/pineal-parenchymal-tumor-of-intermediate-differentiation.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pineal-tumor.html b/concepts/pineal-tumor.html index 6890b75622..e74a6e6180 100644 --- a/concepts/pineal-tumor.html +++ b/concepts/pineal-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pineoblastoma.html b/concepts/pineoblastoma.html index be776a1a06..8e7adecc16 100644 --- a/concepts/pineoblastoma.html +++ b/concepts/pineoblastoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pineocytoma.html b/concepts/pineocytoma.html index d431dc1103..2827aa1a95 100644 --- a/concepts/pineocytoma.html +++ b/concepts/pineocytoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pipobroman.html b/concepts/pipobroman.html index b460324958..7d8e51d484 100644 --- a/concepts/pipobroman.html +++ b/concepts/pipobroman.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pirarubicin.html b/concepts/pirarubicin.html index db7c794719..e11478403d 100644 --- a/concepts/pirarubicin.html +++ b/concepts/pirarubicin.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pituicytoma.html b/concepts/pituicytoma.html index 34402dca9d..aa857dfd9f 100644 --- a/concepts/pituicytoma.html +++ b/concepts/pituicytoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pituitary-adenoma.html b/concepts/pituitary-adenoma.html index 25a8f70765..ffb1c411c2 100644 --- a/concepts/pituitary-adenoma.html +++ b/concepts/pituitary-adenoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pituitary-carcinoma.html b/concepts/pituitary-carcinoma.html index b090bde72b..21b8e0057c 100644 --- a/concepts/pituitary-carcinoma.html +++ b/concepts/pituitary-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/placental-site-trophoblastic-tumor.html b/concepts/placental-site-trophoblastic-tumor.html index a5b84c0011..db8dc6adeb 100644 --- a/concepts/placental-site-trophoblastic-tumor.html +++ b/concepts/placental-site-trophoblastic-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/plasma-cell-myeloma.html b/concepts/plasma-cell-myeloma.html index 9e3101bfdb..9dcafcb721 100644 --- a/concepts/plasma-cell-myeloma.html +++ b/concepts/plasma-cell-myeloma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/plasmablastic-lymphoma.html b/concepts/plasmablastic-lymphoma.html index 6edcd2f064..0ea1ab9430 100644 --- a/concepts/plasmablastic-lymphoma.html +++ b/concepts/plasmablastic-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/plasmacytic-hyperplasia-ptld.html b/concepts/plasmacytic-hyperplasia-ptld.html index bfad7aa37a..b0b3ca65ec 100644 --- a/concepts/plasmacytic-hyperplasia-ptld.html +++ b/concepts/plasmacytic-hyperplasia-ptld.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/plasmacytoid-signet-ring-cell-bladder-carcinoma.html b/concepts/plasmacytoid-signet-ring-cell-bladder-carcinoma.html index 127af07d6f..9113a7134f 100644 --- a/concepts/plasmacytoid-signet-ring-cell-bladder-carcinoma.html +++ b/concepts/plasmacytoid-signet-ring-cell-bladder-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pleomorphic-carcinoma-of-the-lung.html b/concepts/pleomorphic-carcinoma-of-the-lung.html index ab66925981..c9684a0d9e 100644 --- a/concepts/pleomorphic-carcinoma-of-the-lung.html +++ b/concepts/pleomorphic-carcinoma-of-the-lung.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pleomorphic-liposarcoma.html b/concepts/pleomorphic-liposarcoma.html index 87538e7e0d..d33520c313 100644 --- a/concepts/pleomorphic-liposarcoma.html +++ b/concepts/pleomorphic-liposarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pleomorphic-rhabdomyosarcoma.html b/concepts/pleomorphic-rhabdomyosarcoma.html index 4900e8d362..d0ed791eb7 100644 --- a/concepts/pleomorphic-rhabdomyosarcoma.html +++ b/concepts/pleomorphic-rhabdomyosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pleomorphic-xanthoastrocytoma.html b/concepts/pleomorphic-xanthoastrocytoma.html index f97ca7c077..b41d12acb1 100644 --- a/concepts/pleomorphic-xanthoastrocytoma.html +++ b/concepts/pleomorphic-xanthoastrocytoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pleura.html b/concepts/pleura.html index b4c4dce63c..ebb3d154d9 100644 --- a/concepts/pleura.html +++ b/concepts/pleura.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pleural-mesothelioma-biphasic-type.html b/concepts/pleural-mesothelioma-biphasic-type.html index 29df4fdb1e..80696b595f 100644 --- a/concepts/pleural-mesothelioma-biphasic-type.html +++ b/concepts/pleural-mesothelioma-biphasic-type.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pleural-mesothelioma-epithelioid-type.html b/concepts/pleural-mesothelioma-epithelioid-type.html index b4ccde6b3a..f3a84f3679 100644 --- a/concepts/pleural-mesothelioma-epithelioid-type.html +++ b/concepts/pleural-mesothelioma-epithelioid-type.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pleural-mesothelioma-sarcomatoid-type.html b/concepts/pleural-mesothelioma-sarcomatoid-type.html index 066608a4ee..c58e9864e4 100644 --- a/concepts/pleural-mesothelioma-sarcomatoid-type.html +++ b/concepts/pleural-mesothelioma-sarcomatoid-type.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pleural-mesothelioma.html b/concepts/pleural-mesothelioma.html index de03985030..f8c85f7930 100644 --- a/concepts/pleural-mesothelioma.html +++ b/concepts/pleural-mesothelioma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pleuropulmonary-blastoma.html b/concepts/pleuropulmonary-blastoma.html index cb73d82352..d76ac41be0 100644 --- a/concepts/pleuropulmonary-blastoma.html +++ b/concepts/pleuropulmonary-blastoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/polycythaemia-vera-myelofibrosis.html b/concepts/polycythaemia-vera-myelofibrosis.html index b8ace26da1..f6d0a3c0c8 100644 --- a/concepts/polycythaemia-vera-myelofibrosis.html +++ b/concepts/polycythaemia-vera-myelofibrosis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/polycythemia-vera.html b/concepts/polycythemia-vera.html index d22c6be018..d4f09cc9a5 100644 --- a/concepts/polycythemia-vera.html +++ b/concepts/polycythemia-vera.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/polyembryoma-vulva-vagina.html b/concepts/polyembryoma-vulva-vagina.html index 5b349133b3..1217f167a5 100644 --- a/concepts/polyembryoma-vulva-vagina.html +++ b/concepts/polyembryoma-vulva-vagina.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/polyembryoma.html b/concepts/polyembryoma.html index 538a49c8e2..3b6cc16d9c 100644 --- a/concepts/polyembryoma.html +++ b/concepts/polyembryoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/polymorphic-ptld.html b/concepts/polymorphic-ptld.html index 6138aff75d..3b4daf15c3 100644 --- a/concepts/polymorphic-ptld.html +++ b/concepts/polymorphic-ptld.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/poorly-differentiated-carcinoma-nos.html b/concepts/poorly-differentiated-carcinoma-nos.html index ea0afe2654..f1f2fd0c66 100644 --- a/concepts/poorly-differentiated-carcinoma-nos.html +++ b/concepts/poorly-differentiated-carcinoma-nos.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/poorly-differentiated-carcinoma-of-the-stomach.html b/concepts/poorly-differentiated-carcinoma-of-the-stomach.html index b1451551f7..2852f3e46b 100644 --- a/concepts/poorly-differentiated-carcinoma-of-the-stomach.html +++ b/concepts/poorly-differentiated-carcinoma-of-the-stomach.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/poorly-differentiated-carcinoma-of-the-uterus.html b/concepts/poorly-differentiated-carcinoma-of-the-uterus.html index 64ce6a241b..c69a67ff9f 100644 --- a/concepts/poorly-differentiated-carcinoma-of-the-uterus.html +++ b/concepts/poorly-differentiated-carcinoma-of-the-uterus.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/poorly-differentiated-non-small-cell-lung-cancer.html b/concepts/poorly-differentiated-non-small-cell-lung-cancer.html index eae35ec552..2437967c34 100644 --- a/concepts/poorly-differentiated-non-small-cell-lung-cancer.html +++ b/concepts/poorly-differentiated-non-small-cell-lung-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/poorly-differentiated-thyroid-cancer.html b/concepts/poorly-differentiated-thyroid-cancer.html index bd4883c315..7f1d6b5717 100644 --- a/concepts/poorly-differentiated-thyroid-cancer.html +++ b/concepts/poorly-differentiated-thyroid-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/poorly-differentiated-vaginal-carcinoma.html b/concepts/poorly-differentiated-vaginal-carcinoma.html index 2d2db3bfde..5e3c805a70 100644 --- a/concepts/poorly-differentiated-vaginal-carcinoma.html +++ b/concepts/poorly-differentiated-vaginal-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/porocarcinoma-spiroadenocarcinoma.html b/concepts/porocarcinoma-spiroadenocarcinoma.html index fa449ed334..09c21184e5 100644 --- a/concepts/porocarcinoma-spiroadenocarcinoma.html +++ b/concepts/porocarcinoma-spiroadenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/poroma-acrospiroma.html b/concepts/poroma-acrospiroma.html index ba8cc539f2..4c685bfb30 100644 --- a/concepts/poroma-acrospiroma.html +++ b/concepts/poroma-acrospiroma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/porphyria-cutania-tarda.html b/concepts/porphyria-cutania-tarda.html index 8f71af990e..c151ec9a37 100644 --- a/concepts/porphyria-cutania-tarda.html +++ b/concepts/porphyria-cutania-tarda.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/posttransplant-lymphoproliferative-disorders.html b/concepts/posttransplant-lymphoproliferative-disorders.html index eb4a5dee65..9fc54a7ec5 100644 --- a/concepts/posttransplant-lymphoproliferative-disorders.html +++ b/concepts/posttransplant-lymphoproliferative-disorders.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/prevent-cancer-foundation.html b/concepts/prevent-cancer-foundation.html index 67388df707..fe48acd3e6 100644 --- a/concepts/prevent-cancer-foundation.html +++ b/concepts/prevent-cancer-foundation.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/primary-brain-tumor.html b/concepts/primary-brain-tumor.html index fc077bc93d..9f837005ff 100644 --- a/concepts/primary-brain-tumor.html +++ b/concepts/primary-brain-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/primary-cns-melanocytic-tumors.html b/concepts/primary-cns-melanocytic-tumors.html index c9b2440c12..ae288193cf 100644 --- a/concepts/primary-cns-melanocytic-tumors.html +++ b/concepts/primary-cns-melanocytic-tumors.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/primary-cns-melanoma.html b/concepts/primary-cns-melanoma.html index 3d377fb6c2..604b90be9e 100644 --- a/concepts/primary-cns-melanoma.html +++ b/concepts/primary-cns-melanoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/primary-cutaneous-acral-cd8-positive-t-cell-lymphoma.html b/concepts/primary-cutaneous-acral-cd8-positive-t-cell-lymphoma.html index c33a08a51c..5e32d2a2b3 100644 --- a/concepts/primary-cutaneous-acral-cd8-positive-t-cell-lymphoma.html +++ b/concepts/primary-cutaneous-acral-cd8-positive-t-cell-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/primary-cutaneous-anaplastic-large-cell-lymphoma.html b/concepts/primary-cutaneous-anaplastic-large-cell-lymphoma.html index d61ce4c304..814826f113 100644 --- a/concepts/primary-cutaneous-anaplastic-large-cell-lymphoma.html +++ b/concepts/primary-cutaneous-anaplastic-large-cell-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/primary-cutaneous-cd30-positive-t-cell-lymphoproliferative-disorders.html b/concepts/primary-cutaneous-cd30-positive-t-cell-lymphoproliferative-disorders.html index defc33a786..72b06dfd4f 100644 --- a/concepts/primary-cutaneous-cd30-positive-t-cell-lymphoproliferative-disorders.html +++ b/concepts/primary-cutaneous-cd30-positive-t-cell-lymphoproliferative-disorders.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/primary-cutaneous-cd4-positive-small-medium-t-cell-lymphoproliferative-disorder.html b/concepts/primary-cutaneous-cd4-positive-small-medium-t-cell-lymphoproliferative-disorder.html index 43747e7d46..5d3b708d49 100644 --- a/concepts/primary-cutaneous-cd4-positive-small-medium-t-cell-lymphoproliferative-disorder.html +++ b/concepts/primary-cutaneous-cd4-positive-small-medium-t-cell-lymphoproliferative-disorder.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/primary-cutaneous-cd8-positive-aggressive-epidermotropic-cytotoxic-t-cell-lymphoma.html b/concepts/primary-cutaneous-cd8-positive-aggressive-epidermotropic-cytotoxic-t-cell-lymphoma.html index 634b940329..5ef9d360e7 100644 --- a/concepts/primary-cutaneous-cd8-positive-aggressive-epidermotropic-cytotoxic-t-cell-lymphoma.html +++ b/concepts/primary-cutaneous-cd8-positive-aggressive-epidermotropic-cytotoxic-t-cell-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/primary-cutaneous-dlbcl-leg-type.html b/concepts/primary-cutaneous-dlbcl-leg-type.html index 113ba457fd..a58a4650ca 100644 --- a/concepts/primary-cutaneous-dlbcl-leg-type.html +++ b/concepts/primary-cutaneous-dlbcl-leg-type.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/primary-cutaneous-follicle-center-lymphoma.html b/concepts/primary-cutaneous-follicle-center-lymphoma.html index 23d7a6416a..dd6f9e9987 100644 --- a/concepts/primary-cutaneous-follicle-center-lymphoma.html +++ b/concepts/primary-cutaneous-follicle-center-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/primary-cutaneous-gamma-delta-t-cell-lymphoma.html b/concepts/primary-cutaneous-gamma-delta-t-cell-lymphoma.html index 9e9671a4d6..81dff449fc 100644 --- a/concepts/primary-cutaneous-gamma-delta-t-cell-lymphoma.html +++ b/concepts/primary-cutaneous-gamma-delta-t-cell-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/primary-dlbcl-of-the-central-nervous-system.html b/concepts/primary-dlbcl-of-the-central-nervous-system.html index c906ea6842..64f436485c 100644 --- a/concepts/primary-dlbcl-of-the-central-nervous-system.html +++ b/concepts/primary-dlbcl-of-the-central-nervous-system.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/primary-effusion-lymphoma.html b/concepts/primary-effusion-lymphoma.html index 04d42b9873..653dc5f627 100644 --- a/concepts/primary-effusion-lymphoma.html +++ b/concepts/primary-effusion-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/primary-mediastinal-thymic-large-b-cell-lymphoma.html b/concepts/primary-mediastinal-thymic-large-b-cell-lymphoma.html index 2695905725..8bce2bc921 100644 --- a/concepts/primary-mediastinal-thymic-large-b-cell-lymphoma.html +++ b/concepts/primary-mediastinal-thymic-large-b-cell-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/primary-myelofibrosis-prefibrotic-early-stage.html b/concepts/primary-myelofibrosis-prefibrotic-early-stage.html index 05f01a624e..a71e9836c8 100644 --- a/concepts/primary-myelofibrosis-prefibrotic-early-stage.html +++ b/concepts/primary-myelofibrosis-prefibrotic-early-stage.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/primary-myelofibrosis.html b/concepts/primary-myelofibrosis.html index 8e115a106d..fd8ccdda22 100644 --- a/concepts/primary-myelofibrosis.html +++ b/concepts/primary-myelofibrosis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/primary-myelofibrosisovert-fibrotic-stage.html b/concepts/primary-myelofibrosisovert-fibrotic-stage.html index d16b8e1114..f52e00c9cf 100644 --- a/concepts/primary-myelofibrosisovert-fibrotic-stage.html +++ b/concepts/primary-myelofibrosisovert-fibrotic-stage.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/primary-neuroepithelial-tumor.html b/concepts/primary-neuroepithelial-tumor.html index 355217c60f..567b3d6638 100644 --- a/concepts/primary-neuroepithelial-tumor.html +++ b/concepts/primary-neuroepithelial-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/primitive-neuroectodermal-tumor.html b/concepts/primitive-neuroectodermal-tumor.html index 7d1bf46cc2..374bee221b 100644 --- a/concepts/primitive-neuroectodermal-tumor.html +++ b/concepts/primitive-neuroectodermal-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/princess-margaret-cancer-centre.html b/concepts/princess-margaret-cancer-centre.html index e1f417687c..fb5d639083 100644 --- a/concepts/princess-margaret-cancer-centre.html +++ b/concepts/princess-margaret-cancer-centre.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/proliferating-pilar-cystic-tumor.html b/concepts/proliferating-pilar-cystic-tumor.html index 2c83805a2c..470990e1b2 100644 --- a/concepts/proliferating-pilar-cystic-tumor.html +++ b/concepts/proliferating-pilar-cystic-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/prostate-adenocarcinoma.html b/concepts/prostate-adenocarcinoma.html index e9cca51c2b..1c9bfe6eea 100644 --- a/concepts/prostate-adenocarcinoma.html +++ b/concepts/prostate-adenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/prostate-cancer-subreddit.html b/concepts/prostate-cancer-subreddit.html index 0e152440d4..90bca66d85 100644 --- a/concepts/prostate-cancer-subreddit.html +++ b/concepts/prostate-cancer-subreddit.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/prostate-neuroendocrine-carcinoma.html b/concepts/prostate-neuroendocrine-carcinoma.html index bd336afcca..c46a1588fc 100644 --- a/concepts/prostate-neuroendocrine-carcinoma.html +++ b/concepts/prostate-neuroendocrine-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/prostate-small-cell-carcinoma.html b/concepts/prostate-small-cell-carcinoma.html index 29a0a69124..ae0fabcf80 100644 --- a/concepts/prostate-small-cell-carcinoma.html +++ b/concepts/prostate-small-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/prostate-squamous-cell-carcinoma.html b/concepts/prostate-squamous-cell-carcinoma.html index 96aae4f9a2..a5ee7683b1 100644 --- a/concepts/prostate-squamous-cell-carcinoma.html +++ b/concepts/prostate-squamous-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/prostate.html b/concepts/prostate.html index 88c4b79b7d..77330bf55c 100644 --- a/concepts/prostate.html +++ b/concepts/prostate.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/prostatectomy.html b/concepts/prostatectomy.html index 4936b6d655..1bad8d4429 100644 --- a/concepts/prostatectomy.html +++ b/concepts/prostatectomy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/proximal-type-epithelioid-sarcoma.html b/concepts/proximal-type-epithelioid-sarcoma.html index c959da8419..24b4bfbe96 100644 --- a/concepts/proximal-type-epithelioid-sarcoma.html +++ b/concepts/proximal-type-epithelioid-sarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pseudomyogenic-hemangioendothelioma.html b/concepts/pseudomyogenic-hemangioendothelioma.html index 02be7b0024..0917221b45 100644 --- a/concepts/pseudomyogenic-hemangioendothelioma.html +++ b/concepts/pseudomyogenic-hemangioendothelioma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pubmed-central.html b/concepts/pubmed-central.html index eff495da7d..16c8bf2393 100644 --- a/concepts/pubmed-central.html +++ b/concepts/pubmed-central.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pubmed.html b/concepts/pubmed.html index 176f784a0b..07c5cefa08 100644 --- a/concepts/pubmed.html +++ b/concepts/pubmed.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pulmonary-lymphangiomyomatosis.html b/concepts/pulmonary-lymphangiomyomatosis.html index 55436a4835..95cc20aee0 100644 --- a/concepts/pulmonary-lymphangiomyomatosis.html +++ b/concepts/pulmonary-lymphangiomyomatosis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pulmonary-metastasectomy.html b/concepts/pulmonary-metastasectomy.html index 9c0a988e11..7c77b10e71 100644 --- a/concepts/pulmonary-metastasectomy.html +++ b/concepts/pulmonary-metastasectomy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/purdue-institute-for-cancer-research.html b/concepts/purdue-institute-for-cancer-research.html index 709688cf70..9d9056c1e9 100644 --- a/concepts/purdue-institute-for-cancer-research.html +++ b/concepts/purdue-institute-for-cancer-research.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/pure-erythroid-leukemia.html b/concepts/pure-erythroid-leukemia.html index 61eea39836..2f1d658004 100644 --- a/concepts/pure-erythroid-leukemia.html +++ b/concepts/pure-erythroid-leukemia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/qatar-ncc.html b/concepts/qatar-ncc.html index 4440b35e22..c691693d1f 100644 --- a/concepts/qatar-ncc.html +++ b/concepts/qatar-ncc.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/qcancer.html b/concepts/qcancer.html index 4876aa5a65..b329a6f505 100644 --- a/concepts/qcancer.html +++ b/concepts/qcancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/qcr-au.html b/concepts/qcr-au.html index e70af76869..2ad68685a2 100644 --- a/concepts/qcr-au.html +++ b/concepts/qcr-au.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/radiation-associated-sarcoma.html b/concepts/radiation-associated-sarcoma.html index 6ec246598c..92bbc7a0a5 100644 --- a/concepts/radiation-associated-sarcoma.html +++ b/concepts/radiation-associated-sarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/radical-remission-surviving-cancer-against-all-odds.html b/concepts/radical-remission-surviving-cancer-against-all-odds.html index 32ae29ec18..a1d8dd7f79 100644 --- a/concepts/radical-remission-surviving-cancer-against-all-odds.html +++ b/concepts/radical-remission-surviving-cancer-against-all-odds.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/radioembolization.html b/concepts/radioembolization.html index 47be2725d0..cd1b350f95 100644 --- a/concepts/radioembolization.html +++ b/concepts/radioembolization.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/radiofrequency-ablation.html b/concepts/radiofrequency-ablation.html index 3f813baa38..75b64e7668 100644 --- a/concepts/radiofrequency-ablation.html +++ b/concepts/radiofrequency-ablation.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ranimustine.html b/concepts/ranimustine.html index eb909a6b44..2682e392e3 100644 --- a/concepts/ranimustine.html +++ b/concepts/ranimustine.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/rdca-ci.html b/concepts/rdca-ci.html index 2f13d633d3..338becd221 100644 --- a/concepts/rdca-ci.html +++ b/concepts/rdca-ci.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/rdca-dz.html b/concepts/rdca-dz.html index 2cbefa2289..f49ce59078 100644 --- a/concepts/rdca-dz.html +++ b/concepts/rdca-dz.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/rdcb-mo.html b/concepts/rdcb-mo.html index 898c190ead..a28f14d2e6 100644 --- a/concepts/rdcb-mo.html +++ b/concepts/rdcb-mo.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/rdcdb-cg.html b/concepts/rdcdb-cg.html index 2cde026db7..b928b6bb21 100644 --- a/concepts/rdcdb-cg.html +++ b/concepts/rdcdb-cg.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/rdcg-gu.html b/concepts/rdcg-gu.html index f5c62ca5cc..c9cf72f77f 100644 --- a/concepts/rdcg-gu.html +++ b/concepts/rdcg-gu.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/rdclr.html b/concepts/rdclr.html index d42ea0f4ee..938222f8da 100644 --- a/concepts/rdclr.html +++ b/concepts/rdclr.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/rdcm-ma.html b/concepts/rdcm-ma.html index 46d82dd009..4cc80a237f 100644 --- a/concepts/rdcm-ma.html +++ b/concepts/rdcm-ma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/rectal-adenocarcinoma.html b/concepts/rectal-adenocarcinoma.html index abf6ad09bc..7c3e4dd679 100644 --- a/concepts/rectal-adenocarcinoma.html +++ b/concepts/rectal-adenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/refractory-cytopenia-of-childhood.html b/concepts/refractory-cytopenia-of-childhood.html index d05e48be2c..31161acbc4 100644 --- a/concepts/refractory-cytopenia-of-childhood.html +++ b/concepts/refractory-cytopenia-of-childhood.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/regeneron.html b/concepts/regeneron.html index 620c5a95bc..61ac932b0a 100644 --- a/concepts/regeneron.html +++ b/concepts/regeneron.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/regorafenib.html b/concepts/regorafenib.html index fea7507b5f..8d59dfd906 100644 --- a/concepts/regorafenib.html +++ b/concepts/regorafenib.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/renal-angiomyolipoma.html b/concepts/renal-angiomyolipoma.html index a7875a1ee1..1da7f0e9aa 100644 --- a/concepts/renal-angiomyolipoma.html +++ b/concepts/renal-angiomyolipoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/renal-cell-carcinoma.html b/concepts/renal-cell-carcinoma.html index fc5b987206..af84aa0f0e 100644 --- a/concepts/renal-cell-carcinoma.html +++ b/concepts/renal-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/renal-clear-cell-carcinoma-with-sarcomatoid-features.html b/concepts/renal-clear-cell-carcinoma-with-sarcomatoid-features.html index e9ceff6eec..2b53dfb0a8 100644 --- a/concepts/renal-clear-cell-carcinoma-with-sarcomatoid-features.html +++ b/concepts/renal-clear-cell-carcinoma-with-sarcomatoid-features.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/renal-clear-cell-carcinoma.html b/concepts/renal-clear-cell-carcinoma.html index 1016c0c9d5..f69af7cd6f 100644 --- a/concepts/renal-clear-cell-carcinoma.html +++ b/concepts/renal-clear-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/renal-medullary-carcinoma.html b/concepts/renal-medullary-carcinoma.html index ecceacf47d..70ffae466e 100644 --- a/concepts/renal-medullary-carcinoma.html +++ b/concepts/renal-medullary-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/renal-mucinous-tubular-spindle-cell-carcinoma.html b/concepts/renal-mucinous-tubular-spindle-cell-carcinoma.html index 06a05d61b2..d581dcd31e 100644 --- a/concepts/renal-mucinous-tubular-spindle-cell-carcinoma.html +++ b/concepts/renal-mucinous-tubular-spindle-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/renal-neuroendocrine-tumor.html b/concepts/renal-neuroendocrine-tumor.html index bb8baa28f8..3c5d70ebe5 100644 --- a/concepts/renal-neuroendocrine-tumor.html +++ b/concepts/renal-neuroendocrine-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/renal-non-clear-cell-carcinoma.html b/concepts/renal-non-clear-cell-carcinoma.html index 2c21e27f3e..4ed6dc6f34 100644 --- a/concepts/renal-non-clear-cell-carcinoma.html +++ b/concepts/renal-non-clear-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/renal-oncocytoma.html b/concepts/renal-oncocytoma.html index 0fae02bada..495706a09a 100644 --- a/concepts/renal-oncocytoma.html +++ b/concepts/renal-oncocytoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/renal-small-cell-carcinoma.html b/concepts/renal-small-cell-carcinoma.html index 3eb533036a..772e69469e 100644 --- a/concepts/renal-small-cell-carcinoma.html +++ b/concepts/renal-small-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/retinoblastoma.html b/concepts/retinoblastoma.html index a74a91c07d..3381ed9005 100644 --- a/concepts/retinoblastoma.html +++ b/concepts/retinoblastoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/rhabdoid-cancer.html b/concepts/rhabdoid-cancer.html index 08d37bd7e0..d95e09dd30 100644 --- a/concepts/rhabdoid-cancer.html +++ b/concepts/rhabdoid-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/rhabdoid-meningioma.html b/concepts/rhabdoid-meningioma.html index ed35707c43..8bd83f111a 100644 --- a/concepts/rhabdoid-meningioma.html +++ b/concepts/rhabdoid-meningioma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/rhabdomyosarcoma.html b/concepts/rhabdomyosarcoma.html index 2f2ae509fb..10021f16d8 100644 --- a/concepts/rhabdomyosarcoma.html +++ b/concepts/rhabdomyosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/rivkin-center-risk-assessment-tool.html b/concepts/rivkin-center-risk-assessment-tool.html index c92a5edad0..8dc545fd0a 100644 --- a/concepts/rivkin-center-risk-assessment-tool.html +++ b/concepts/rivkin-center-risk-assessment-tool.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/robert-h-lurie-comprehensive-cancer-center.html b/concepts/robert-h-lurie-comprehensive-cancer-center.html index e266db930c..db04f08da4 100644 --- a/concepts/robert-h-lurie-comprehensive-cancer-center.html +++ b/concepts/robert-h-lurie-comprehensive-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/robotic-assisted-bronchoscopy.html b/concepts/robotic-assisted-bronchoscopy.html index 5825aa9c3b..d401bb6a4e 100644 --- a/concepts/robotic-assisted-bronchoscopy.html +++ b/concepts/robotic-assisted-bronchoscopy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/roche.html b/concepts/roche.html index 0299242d26..487cb5b871 100644 --- a/concepts/roche.html +++ b/concepts/roche.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/romidepsin.html b/concepts/romidepsin.html index 457fb4c69a..a8f056961e 100644 --- a/concepts/romidepsin.html +++ b/concepts/romidepsin.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/rosai-dorfman-disease.html b/concepts/rosai-dorfman-disease.html index f155f31e9a..e31799eb32 100644 --- a/concepts/rosai-dorfman-disease.html +++ b/concepts/rosai-dorfman-disease.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/rosette-forming-glioneuronal-tumor-of-the-fourth-ventricle.html b/concepts/rosette-forming-glioneuronal-tumor-of-the-fourth-ventricle.html index 97a4d1a668..213a963551 100644 --- a/concepts/rosette-forming-glioneuronal-tumor-of-the-fourth-ventricle.html +++ b/concepts/rosette-forming-glioneuronal-tumor-of-the-fourth-ventricle.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/roswell-park-comprehensive-cancer-center.html b/concepts/roswell-park-comprehensive-cancer-center.html index ca6500f94b..6980118a23 100644 --- a/concepts/roswell-park-comprehensive-cancer-center.html +++ b/concepts/roswell-park-comprehensive-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/round-cell-sarcoma-nos.html b/concepts/round-cell-sarcoma-nos.html index 230e60f4aa..620fc72782 100644 --- a/concepts/round-cell-sarcoma-nos.html +++ b/concepts/round-cell-sarcoma-nos.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/rpcc.html b/concepts/rpcc.html index 04ed464475..3d3be7df65 100644 --- a/concepts/rpcc.html +++ b/concepts/rpcc.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/rpt-ar.html b/concepts/rpt-ar.html index 8685274315..c1eceaf5ea 100644 --- a/concepts/rpt-ar.html +++ b/concepts/rpt-ar.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/rush-university-medical-center.html b/concepts/rush-university-medical-center.html index 3a4ace9291..605436c743 100644 --- a/concepts/rush-university-medical-center.html +++ b/concepts/rush-university-medical-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/rutgers-cancer-institute-of-new-jersey.html b/concepts/rutgers-cancer-institute-of-new-jersey.html index e69b68b5d7..4ec9e898d7 100644 --- a/concepts/rutgers-cancer-institute-of-new-jersey.html +++ b/concepts/rutgers-cancer-institute-of-new-jersey.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sacr-au.html b/concepts/sacr-au.html index eb4ca60d7d..9db81bf1a0 100644 --- a/concepts/sacr-au.html +++ b/concepts/sacr-au.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/saecpcr-za.html b/concepts/saecpcr-za.html index 2b6afc1317..b7d0d3f8fe 100644 --- a/concepts/saecpcr-za.html +++ b/concepts/saecpcr-za.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/salivary-adenocarcinoma.html b/concepts/salivary-adenocarcinoma.html index 0acff59f78..2e1fa3745d 100644 --- a/concepts/salivary-adenocarcinoma.html +++ b/concepts/salivary-adenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/salivary-carcinoma-other.html b/concepts/salivary-carcinoma-other.html index d1e5ec0e6c..603e338407 100644 --- a/concepts/salivary-carcinoma-other.html +++ b/concepts/salivary-carcinoma-other.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/salivary-carcinoma.html b/concepts/salivary-carcinoma.html index 3d382be6e1..53fd4ff696 100644 --- a/concepts/salivary-carcinoma.html +++ b/concepts/salivary-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/salivary-duct-carcinoma.html b/concepts/salivary-duct-carcinoma.html index 0776df94d7..0aa2958129 100644 --- a/concepts/salivary-duct-carcinoma.html +++ b/concepts/salivary-duct-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/salivary-gland-oncocytoma.html b/concepts/salivary-gland-oncocytoma.html index 983128f5a3..6e92f5a6d7 100644 --- a/concepts/salivary-gland-oncocytoma.html +++ b/concepts/salivary-gland-oncocytoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/salivary-gland-type-tumor-of-the-lung.html b/concepts/salivary-gland-type-tumor-of-the-lung.html index 7ed36bf7ef..11766f98c9 100644 --- a/concepts/salivary-gland-type-tumor-of-the-lung.html +++ b/concepts/salivary-gland-type-tumor-of-the-lung.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/salk-institute-cancer-center.html b/concepts/salk-institute-cancer-center.html index eb55ac8b58..045d0a7f19 100644 --- a/concepts/salk-institute-cancer-center.html +++ b/concepts/salk-institute-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sanford-burnham-prebys-medical-discovery-institute.html b/concepts/sanford-burnham-prebys-medical-discovery-institute.html index 53e598fafc..a90156bf6d 100644 --- a/concepts/sanford-burnham-prebys-medical-discovery-institute.html +++ b/concepts/sanford-burnham-prebys-medical-discovery-institute.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sanofi.html b/concepts/sanofi.html index 8b2dd4ade8..8fadbd8064 100644 --- a/concepts/sanofi.html +++ b/concepts/sanofi.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sarcoma-nos.html b/concepts/sarcoma-nos.html index 1abdafd81b..199336f660 100644 --- a/concepts/sarcoma-nos.html +++ b/concepts/sarcoma-nos.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sarcomatoid-carcinoma-of-the-lung.html b/concepts/sarcomatoid-carcinoma-of-the-lung.html index a1ce22cd37..c06cd13373 100644 --- a/concepts/sarcomatoid-carcinoma-of-the-lung.html +++ b/concepts/sarcomatoid-carcinoma-of-the-lung.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sarcomatoid-carcinoma-of-the-urinary-bladder.html b/concepts/sarcomatoid-carcinoma-of-the-urinary-bladder.html index dd29b81b30..fd4c2627a8 100644 --- a/concepts/sarcomatoid-carcinoma-of-the-urinary-bladder.html +++ b/concepts/sarcomatoid-carcinoma-of-the-urinary-bladder.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sarcomatoid-renal-cell-carcinoma.html b/concepts/sarcomatoid-renal-cell-carcinoma.html index 765811024a..2bf4f02cac 100644 --- a/concepts/sarcomatoid-renal-cell-carcinoma.html +++ b/concepts/sarcomatoid-renal-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/satraplatin.html b/concepts/satraplatin.html index 832ceff3c4..dd372449f8 100644 --- a/concepts/satraplatin.html +++ b/concepts/satraplatin.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sauna.html b/concepts/sauna.html index f55715c07d..1ee0d31189 100644 --- a/concepts/sauna.html +++ b/concepts/sauna.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sbrt.html b/concepts/sbrt.html index 9c2f9a7500..bbc90f7047 100644 --- a/concepts/sbrt.html +++ b/concepts/sbrt.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/schwannoma.html b/concepts/schwannoma.html index 9b486a500d..5c290d8019 100644 --- a/concepts/schwannoma.html +++ b/concepts/schwannoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sclerosing-epithelioid-fibrosarcoma.html b/concepts/sclerosing-epithelioid-fibrosarcoma.html index 0e19272319..2aca1c60ab 100644 --- a/concepts/sclerosing-epithelioid-fibrosarcoma.html +++ b/concepts/sclerosing-epithelioid-fibrosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/scr-dz.html b/concepts/scr-dz.html index f260166d8c..f9f9b22909 100644 --- a/concepts/scr-dz.html +++ b/concepts/scr-dz.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/seattle-childrens-hospital.html b/concepts/seattle-childrens-hospital.html index 70c3de4692..f8d429e1ea 100644 --- a/concepts/seattle-childrens-hospital.html +++ b/concepts/seattle-childrens-hospital.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sebaceous-carcinoma.html b/concepts/sebaceous-carcinoma.html index a90f3e4838..6f967b66c3 100644 --- a/concepts/sebaceous-carcinoma.html +++ b/concepts/sebaceous-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/secondary-osteosarcoma.html b/concepts/secondary-osteosarcoma.html index 82b957efbc..2e96c9a208 100644 --- a/concepts/secondary-osteosarcoma.html +++ b/concepts/secondary-osteosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/seer.html b/concepts/seer.html index a9616c4eb0..f3e750cd1e 100644 --- a/concepts/seer.html +++ b/concepts/seer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sellar-tumor.html b/concepts/sellar-tumor.html index b5d9a7e921..bf703b4d76 100644 --- a/concepts/sellar-tumor.html +++ b/concepts/sellar-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/seminoma.html b/concepts/seminoma.html index d8ac0afd95..03501ec45c 100644 --- a/concepts/seminoma.html +++ b/concepts/seminoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/semustine.html b/concepts/semustine.html index 1d3b7616ef..060e89fa27 100644 --- a/concepts/semustine.html +++ b/concepts/semustine.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/serene-hospice-care.html b/concepts/serene-hospice-care.html index 402262f7a4..3d6a050ab9 100644 --- a/concepts/serene-hospice-care.html +++ b/concepts/serene-hospice-care.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/serous-borderline-ovarian-tumor-micropapillary.html b/concepts/serous-borderline-ovarian-tumor-micropapillary.html index 948d76520d..779ad089d1 100644 --- a/concepts/serous-borderline-ovarian-tumor-micropapillary.html +++ b/concepts/serous-borderline-ovarian-tumor-micropapillary.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/serous-borderline-ovarian-tumor.html b/concepts/serous-borderline-ovarian-tumor.html index 9252d8a341..e2f1c3ed4c 100644 --- a/concepts/serous-borderline-ovarian-tumor.html +++ b/concepts/serous-borderline-ovarian-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/serous-cystadenoma-of-the-pancreas.html b/concepts/serous-cystadenoma-of-the-pancreas.html index 852e1db076..bf8f9d2ad1 100644 --- a/concepts/serous-cystadenoma-of-the-pancreas.html +++ b/concepts/serous-cystadenoma-of-the-pancreas.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/serous-ovarian-cancer.html b/concepts/serous-ovarian-cancer.html index bb91a64e80..03cd53988b 100644 --- a/concepts/serous-ovarian-cancer.html +++ b/concepts/serous-ovarian-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sertoli-leydig-cell-tumor.html b/concepts/sertoli-leydig-cell-tumor.html index 6db6cd66db..f12b8c6c44 100644 --- a/concepts/sertoli-leydig-cell-tumor.html +++ b/concepts/sertoli-leydig-cell-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sex-cord-stromal-tumor-testis.html b/concepts/sex-cord-stromal-tumor-testis.html index 98be54c957..d8be5485b9 100644 --- a/concepts/sex-cord-stromal-tumor-testis.html +++ b/concepts/sex-cord-stromal-tumor-testis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sex-cord-stromal-tumor.html b/concepts/sex-cord-stromal-tumor.html index 99260314e8..1e9286af20 100644 --- a/concepts/sex-cord-stromal-tumor.html +++ b/concepts/sex-cord-stromal-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sezary-syndrome.html b/concepts/sezary-syndrome.html index 8e83341414..5f7018e6fe 100644 --- a/concepts/sezary-syndrome.html +++ b/concepts/sezary-syndrome.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sialoblastoma.html b/concepts/sialoblastoma.html index 709e88f804..a887e02ff6 100644 --- a/concepts/sialoblastoma.html +++ b/concepts/sialoblastoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sidney-kimmel-cancer-center-at-thomas-jefferson-university.html b/concepts/sidney-kimmel-cancer-center-at-thomas-jefferson-university.html index 715c44c853..e5356c7754 100644 --- a/concepts/sidney-kimmel-cancer-center-at-thomas-jefferson-university.html +++ b/concepts/sidney-kimmel-cancer-center-at-thomas-jefferson-university.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sidney-kimmel-comprehensive-cancer-center.html b/concepts/sidney-kimmel-comprehensive-cancer-center.html index 0579d1225a..bb84a4164a 100644 --- a/concepts/sidney-kimmel-comprehensive-cancer-center.html +++ b/concepts/sidney-kimmel-comprehensive-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sigmoidoscopy.html b/concepts/sigmoidoscopy.html index a7fe3be1be..614855075a 100644 --- a/concepts/sigmoidoscopy.html +++ b/concepts/sigmoidoscopy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/signet-ring-cell-adenocarcinoma-of-the-colon-and-rectum.html b/concepts/signet-ring-cell-adenocarcinoma-of-the-colon-and-rectum.html index 560f9536a9..01e7945ee0 100644 --- a/concepts/signet-ring-cell-adenocarcinoma-of-the-colon-and-rectum.html +++ b/concepts/signet-ring-cell-adenocarcinoma-of-the-colon-and-rectum.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/signet-ring-cell-carcinoma-of-the-stomach.html b/concepts/signet-ring-cell-carcinoma-of-the-stomach.html index 7b5264a0ba..fb56f6ea48 100644 --- a/concepts/signet-ring-cell-carcinoma-of-the-stomach.html +++ b/concepts/signet-ring-cell-carcinoma-of-the-stomach.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/signet-ring-cell-type-of-the-appendix.html b/concepts/signet-ring-cell-type-of-the-appendix.html index 5fba665389..60d92e847a 100644 --- a/concepts/signet-ring-cell-type-of-the-appendix.html +++ b/concepts/signet-ring-cell-type-of-the-appendix.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/signet-ring-mucinous-carcinoma.html b/concepts/signet-ring-mucinous-carcinoma.html index 7e3345cd29..cf3743ff7d 100644 --- a/concepts/signet-ring-mucinous-carcinoma.html +++ b/concepts/signet-ring-mucinous-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sinonasal-adenocarcinoma.html b/concepts/sinonasal-adenocarcinoma.html index 19e845d2cd..98933ddef0 100644 --- a/concepts/sinonasal-adenocarcinoma.html +++ b/concepts/sinonasal-adenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sinonasal-squamous-cell-carcinoma.html b/concepts/sinonasal-squamous-cell-carcinoma.html index e0cf030832..be52ae2e0f 100644 --- a/concepts/sinonasal-squamous-cell-carcinoma.html +++ b/concepts/sinonasal-squamous-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sinonasal-undifferentiated-carcinoma.html b/concepts/sinonasal-undifferentiated-carcinoma.html index 7772a79fef..3f7166fe53 100644 --- a/concepts/sinonasal-undifferentiated-carcinoma.html +++ b/concepts/sinonasal-undifferentiated-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sinopharm.html b/concepts/sinopharm.html index c6fe23c233..ad25c79f1b 100644 --- a/concepts/sinopharm.html +++ b/concepts/sinopharm.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sitc.html b/concepts/sitc.html index 3ee52d231f..0ef5eed6d8 100644 --- a/concepts/sitc.html +++ b/concepts/sitc.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/skin-adnexal-carcinoma.html b/concepts/skin-adnexal-carcinoma.html index 62d37fa369..716f21dec1 100644 --- a/concepts/skin-adnexal-carcinoma.html +++ b/concepts/skin-adnexal-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/skin-cancer-subreddit.html b/concepts/skin-cancer-subreddit.html index 0dcaacdb85..5c802ef1ca 100644 --- a/concepts/skin-cancer-subreddit.html +++ b/concepts/skin-cancer-subreddit.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/skin.html b/concepts/skin.html index 618c6816bb..d27a4a60a8 100644 --- a/concepts/skin.html +++ b/concepts/skin.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sleep.html b/concepts/sleep.html index 95897cf654..5884854bc3 100644 --- a/concepts/sleep.html +++ b/concepts/sleep.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/small-bowel-cancer.html b/concepts/small-bowel-cancer.html index 666acc5cc7..42f68962c5 100644 --- a/concepts/small-bowel-cancer.html +++ b/concepts/small-bowel-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/small-bowel-well-differentiated-neuroendocrine-tumor.html b/concepts/small-bowel-well-differentiated-neuroendocrine-tumor.html index 2a8e6589d4..20f6f0fadf 100644 --- a/concepts/small-bowel-well-differentiated-neuroendocrine-tumor.html +++ b/concepts/small-bowel-well-differentiated-neuroendocrine-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/small-cell-bladder-cancer.html b/concepts/small-cell-bladder-cancer.html index 15640041cd..891e89ce01 100644 --- a/concepts/small-cell-bladder-cancer.html +++ b/concepts/small-cell-bladder-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/small-cell-carcinoma-of-the-cervix.html b/concepts/small-cell-carcinoma-of-the-cervix.html index ffc6a45543..382423ef90 100644 --- a/concepts/small-cell-carcinoma-of-the-cervix.html +++ b/concepts/small-cell-carcinoma-of-the-cervix.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/small-cell-carcinoma-of-the-ovary.html b/concepts/small-cell-carcinoma-of-the-ovary.html index 6bee3e1d99..bed15c6025 100644 --- a/concepts/small-cell-carcinoma-of-the-ovary.html +++ b/concepts/small-cell-carcinoma-of-the-ovary.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/small-cell-carcinoma-of-the-stomach.html b/concepts/small-cell-carcinoma-of-the-stomach.html index bd293f3f14..193ebf91a3 100644 --- a/concepts/small-cell-carcinoma-of-the-stomach.html +++ b/concepts/small-cell-carcinoma-of-the-stomach.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/small-cell-carcinoma-of-unknown-primary.html b/concepts/small-cell-carcinoma-of-unknown-primary.html index 63bf0d648c..0f21e03578 100644 --- a/concepts/small-cell-carcinoma-of-unknown-primary.html +++ b/concepts/small-cell-carcinoma-of-unknown-primary.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/small-cell-gallbladder-carcinoma.html b/concepts/small-cell-gallbladder-carcinoma.html index 41c381eb5e..d4ddfd7811 100644 --- a/concepts/small-cell-gallbladder-carcinoma.html +++ b/concepts/small-cell-gallbladder-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/small-cell-glioblastoma.html b/concepts/small-cell-glioblastoma.html index 8bee0a064f..5ecff96149 100644 --- a/concepts/small-cell-glioblastoma.html +++ b/concepts/small-cell-glioblastoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/small-cell-lung-cancer.html b/concepts/small-cell-lung-cancer.html index 772ab199f8..330070b06a 100644 --- a/concepts/small-cell-lung-cancer.html +++ b/concepts/small-cell-lung-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/small-cell-osteosarcoma.html b/concepts/small-cell-osteosarcoma.html index 4c4bf7e0cf..32f2d9e9ee 100644 --- a/concepts/small-cell-osteosarcoma.html +++ b/concepts/small-cell-osteosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/small-intestinal-carcinoma.html b/concepts/small-intestinal-carcinoma.html index 8cabde0ee5..d158d75d91 100644 --- a/concepts/small-intestinal-carcinoma.html +++ b/concepts/small-intestinal-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/smoking-cessation.html b/concepts/smoking-cessation.html index ef0a308ebf..4c71eb107a 100644 --- a/concepts/smoking-cessation.html +++ b/concepts/smoking-cessation.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/smoldering-systemic-mastocytosis.html b/concepts/smoldering-systemic-mastocytosis.html index c88a86bbb6..41393f6550 100644 --- a/concepts/smoldering-systemic-mastocytosis.html +++ b/concepts/smoldering-systemic-mastocytosis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/smooth-muscle-neoplasm-nos.html b/concepts/smooth-muscle-neoplasm-nos.html index 7d274feb4a..6966efa231 100644 --- a/concepts/smooth-muscle-neoplasm-nos.html +++ b/concepts/smooth-muscle-neoplasm-nos.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sncr-sy.html b/concepts/sncr-sy.html index 2e2d31379a..a8d08fb839 100644 --- a/concepts/sncr-sy.html +++ b/concepts/sncr-sy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/soft-tissue-myoepithelial-carcinoma.html b/concepts/soft-tissue-myoepithelial-carcinoma.html index 9c2025e8df..d1c8ba13ee 100644 --- a/concepts/soft-tissue-myoepithelial-carcinoma.html +++ b/concepts/soft-tissue-myoepithelial-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/soft-tissue.html b/concepts/soft-tissue.html index ca7046bd32..9a1402d142 100644 --- a/concepts/soft-tissue.html +++ b/concepts/soft-tissue.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/solid-papillary-carcinoma-of-the-breast.html b/concepts/solid-papillary-carcinoma-of-the-breast.html index 0e75f2d45a..a61aeb70f5 100644 --- a/concepts/solid-papillary-carcinoma-of-the-breast.html +++ b/concepts/solid-papillary-carcinoma-of-the-breast.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/solid-pseudopapillary-neoplasm-of-the-pancreas.html b/concepts/solid-pseudopapillary-neoplasm-of-the-pancreas.html index bf41c5d04c..64263fad2f 100644 --- a/concepts/solid-pseudopapillary-neoplasm-of-the-pancreas.html +++ b/concepts/solid-pseudopapillary-neoplasm-of-the-pancreas.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/solitary-fibrous-tumor-hemangiopericytoma.html b/concepts/solitary-fibrous-tumor-hemangiopericytoma.html index 33e26c4521..48a62e1858 100644 --- a/concepts/solitary-fibrous-tumor-hemangiopericytoma.html +++ b/concepts/solitary-fibrous-tumor-hemangiopericytoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/solitary-fibrous-tumor-of-the-central-nervous-system.html b/concepts/solitary-fibrous-tumor-of-the-central-nervous-system.html index 8407a0b494..ee705f38b9 100644 --- a/concepts/solitary-fibrous-tumor-of-the-central-nervous-system.html +++ b/concepts/solitary-fibrous-tumor-of-the-central-nervous-system.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/solitary-plasmacytoma-of-bone.html b/concepts/solitary-plasmacytoma-of-bone.html index 712be1ffd1..b7be66482a 100644 --- a/concepts/solitary-plasmacytoma-of-bone.html +++ b/concepts/solitary-plasmacytoma-of-bone.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sorafenib.html b/concepts/sorafenib.html index b49139abcb..388e7cfd36 100644 --- a/concepts/sorafenib.html +++ b/concepts/sorafenib.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/spindle-cell-carcinoma-of-the-lung.html b/concepts/spindle-cell-carcinoma-of-the-lung.html index 44c064f40c..0b255976e4 100644 --- a/concepts/spindle-cell-carcinoma-of-the-lung.html +++ b/concepts/spindle-cell-carcinoma-of-the-lung.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/spindle-cell-oncocytoma-of-the-adenohypophysis.html b/concepts/spindle-cell-oncocytoma-of-the-adenohypophysis.html index ed03234927..6c029a6317 100644 --- a/concepts/spindle-cell-oncocytoma-of-the-adenohypophysis.html +++ b/concepts/spindle-cell-oncocytoma-of-the-adenohypophysis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/spindle-cell-rhabdomyosarcoma.html b/concepts/spindle-cell-rhabdomyosarcoma.html index ee9656ab8f..44edd6b69a 100644 --- a/concepts/spindle-cell-rhabdomyosarcoma.html +++ b/concepts/spindle-cell-rhabdomyosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/spindle-cell-sclerosing-rhabdomyosarcoma.html b/concepts/spindle-cell-sclerosing-rhabdomyosarcoma.html index 10d96502e6..453a9105e1 100644 --- a/concepts/spindle-cell-sclerosing-rhabdomyosarcoma.html +++ b/concepts/spindle-cell-sclerosing-rhabdomyosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/spiroma-spiradenoma.html b/concepts/spiroma-spiradenoma.html index ffc0181b36..ff8d58301e 100644 --- a/concepts/spiroma-spiradenoma.html +++ b/concepts/spiroma-spiradenoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/spitzoid-melanoma.html b/concepts/spitzoid-melanoma.html index 0a59098859..9eee8faa66 100644 --- a/concepts/spitzoid-melanoma.html +++ b/concepts/spitzoid-melanoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/splenectomy.html b/concepts/splenectomy.html index f73cae8acd..8f4e022c8f 100644 --- a/concepts/splenectomy.html +++ b/concepts/splenectomy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/splenic-b-cell-lymphoma-leukemia-unclassifiable.html b/concepts/splenic-b-cell-lymphoma-leukemia-unclassifiable.html index 2d7ff1c2b6..5debea0e0b 100644 --- a/concepts/splenic-b-cell-lymphoma-leukemia-unclassifiable.html +++ b/concepts/splenic-b-cell-lymphoma-leukemia-unclassifiable.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/splenic-diffuse-red-pulp-small-b-cell-lymphoma.html b/concepts/splenic-diffuse-red-pulp-small-b-cell-lymphoma.html index 98c6613369..2c7207c165 100644 --- a/concepts/splenic-diffuse-red-pulp-small-b-cell-lymphoma.html +++ b/concepts/splenic-diffuse-red-pulp-small-b-cell-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/splenic-marginal-zone-lymphoma.html b/concepts/splenic-marginal-zone-lymphoma.html index ba68f69e38..4b5c38b7bf 100644 --- a/concepts/splenic-marginal-zone-lymphoma.html +++ b/concepts/splenic-marginal-zone-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/squamous-cell-carcinoma-of-the-vulva-vagina.html b/concepts/squamous-cell-carcinoma-of-the-vulva-vagina.html index b4f67bade7..8748e79b15 100644 --- a/concepts/squamous-cell-carcinoma-of-the-vulva-vagina.html +++ b/concepts/squamous-cell-carcinoma-of-the-vulva-vagina.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/squamous-cell-carcinoma.html b/concepts/squamous-cell-carcinoma.html index c1b320659a..76f64ce007 100644 --- a/concepts/squamous-cell-carcinoma.html +++ b/concepts/squamous-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/st-jude-childrens-research-hospital.html b/concepts/st-jude-childrens-research-hospital.html index 1d02f16346..3aeb2404b5 100644 --- a/concepts/st-jude-childrens-research-hospital.html +++ b/concepts/st-jude-childrens-research-hospital.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/stanford-cancer-institute-sci.html b/concepts/stanford-cancer-institute-sci.html index 4789f514b1..112842a86b 100644 --- a/concepts/stanford-cancer-institute-sci.html +++ b/concepts/stanford-cancer-institute-sci.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/steam-bath.html b/concepts/steam-bath.html index e48b5c1b5d..e013f02777 100644 --- a/concepts/steam-bath.html +++ b/concepts/steam-bath.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/stephenson-cancer-center.html b/concepts/stephenson-cancer-center.html index deae9e0535..a0fac3f1a7 100644 --- a/concepts/stephenson-cancer-center.html +++ b/concepts/stephenson-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/steroid-cell-tumor-nos.html b/concepts/steroid-cell-tumor-nos.html index c0986dfadc..70521478a5 100644 --- a/concepts/steroid-cell-tumor-nos.html +++ b/concepts/steroid-cell-tumor-nos.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/stomach-adenocarcinoma.html b/concepts/stomach-adenocarcinoma.html index 35d75ac9ce..f085e416ea 100644 --- a/concepts/stomach-adenocarcinoma.html +++ b/concepts/stomach-adenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/stomach-cancer.html b/concepts/stomach-cancer.html index cd2100f440..23bca249a2 100644 --- a/concepts/stomach-cancer.html +++ b/concepts/stomach-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/streptozotocin.html b/concepts/streptozotocin.html index a59bee7a98..f444f43c82 100644 --- a/concepts/streptozotocin.html +++ b/concepts/streptozotocin.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/stress-management.html b/concepts/stress-management.html index e3a454cd44..f13e252730 100644 --- a/concepts/stress-management.html +++ b/concepts/stress-management.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/subcutaneous-administration.html b/concepts/subcutaneous-administration.html index 4a5aa71577..cd4ac737ae 100644 --- a/concepts/subcutaneous-administration.html +++ b/concepts/subcutaneous-administration.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/subcutaneous-panniculitis-like-t-cell-lymphoma.html b/concepts/subcutaneous-panniculitis-like-t-cell-lymphoma.html index 5ea13be766..b88698a1d5 100644 --- a/concepts/subcutaneous-panniculitis-like-t-cell-lymphoma.html +++ b/concepts/subcutaneous-panniculitis-like-t-cell-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/subependymoma.html b/concepts/subependymoma.html index eb501a247e..d89ec969f7 100644 --- a/concepts/subependymoma.html +++ b/concepts/subependymoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/susan-g-komen.html b/concepts/susan-g-komen.html index 1404d25912..97e9df91ea 100644 --- a/concepts/susan-g-komen.html +++ b/concepts/susan-g-komen.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sweat-gland-adenocarcinoma.html b/concepts/sweat-gland-adenocarcinoma.html index 91b3341620..ee9abc64cb 100644 --- a/concepts/sweat-gland-adenocarcinoma.html +++ b/concepts/sweat-gland-adenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sweat-gland-carcinoma-apocrine-eccrine-carcinoma.html b/concepts/sweat-gland-carcinoma-apocrine-eccrine-carcinoma.html index 13dfd4d53c..b93a48d86a 100644 --- a/concepts/sweat-gland-carcinoma-apocrine-eccrine-carcinoma.html +++ b/concepts/sweat-gland-carcinoma-apocrine-eccrine-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sweden-nch.html b/concepts/sweden-nch.html index 09a51066b7..13acb4b900 100644 --- a/concepts/sweden-nch.html +++ b/concepts/sweden-nch.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/swedish-cancer-institute.html b/concepts/swedish-cancer-institute.html index f1cec52a7e..9828434ee6 100644 --- a/concepts/swedish-cancer-institute.html +++ b/concepts/swedish-cancer-institute.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/swedish-national-cancer-register.html b/concepts/swedish-national-cancer-register.html index 51322c193d..2bcbe3201d 100644 --- a/concepts/swedish-national-cancer-register.html +++ b/concepts/swedish-national-cancer-register.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/swiss-cancer-league.html b/concepts/swiss-cancer-league.html index da6c41fbb0..f93efb0797 100644 --- a/concepts/swiss-cancer-league.html +++ b/concepts/swiss-cancer-league.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/swog.html b/concepts/swog.html index 2e690bb89e..6496e61c9e 100644 --- a/concepts/swog.html +++ b/concepts/swog.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/sylvester-comprehensive-cancer-center.html b/concepts/sylvester-comprehensive-cancer-center.html index 71f6cbb93d..5b758c02d1 100644 --- a/concepts/sylvester-comprehensive-cancer-center.html +++ b/concepts/sylvester-comprehensive-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/synovial-sarcoma.html b/concepts/synovial-sarcoma.html index f2e79b6d51..d9f37620af 100644 --- a/concepts/synovial-sarcoma.html +++ b/concepts/synovial-sarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/systemic-ebv-positive-t-cell-lymphoma-of-childhood.html b/concepts/systemic-ebv-positive-t-cell-lymphoma-of-childhood.html index 9c86ad5d08..1716419263 100644 --- a/concepts/systemic-ebv-positive-t-cell-lymphoma-of-childhood.html +++ b/concepts/systemic-ebv-positive-t-cell-lymphoma-of-childhood.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/systemic-mastocytosis-with-an-associated-hematological-neoplasm.html b/concepts/systemic-mastocytosis-with-an-associated-hematological-neoplasm.html index 646a22e0fa..35e60d8c8d 100644 --- a/concepts/systemic-mastocytosis-with-an-associated-hematological-neoplasm.html +++ b/concepts/systemic-mastocytosis-with-an-associated-hematological-neoplasm.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/systemic-mastocytosis.html b/concepts/systemic-mastocytosis.html index 07e06ab4b0..339cf95045 100644 --- a/concepts/systemic-mastocytosis.html +++ b/concepts/systemic-mastocytosis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/t-cell-histiocyte-rich-large-b-cell-lymphoma.html b/concepts/t-cell-histiocyte-rich-large-b-cell-lymphoma.html index 20194e502f..e85e19bb53 100644 --- a/concepts/t-cell-histiocyte-rich-large-b-cell-lymphoma.html +++ b/concepts/t-cell-histiocyte-rich-large-b-cell-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/t-cell-large-granular-lymphocytic-leukemia.html b/concepts/t-cell-large-granular-lymphocytic-leukemia.html index c5e3fae639..064f41f0c8 100644 --- a/concepts/t-cell-large-granular-lymphocytic-leukemia.html +++ b/concepts/t-cell-large-granular-lymphocytic-leukemia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/t-cell-prolymphocytic-leukemia.html b/concepts/t-cell-prolymphocytic-leukemia.html index 28288fbf91..34a13196b5 100644 --- a/concepts/t-cell-prolymphocytic-leukemia.html +++ b/concepts/t-cell-prolymphocytic-leukemia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/t-lymphoblastic-leukemia-lymphoma.html b/concepts/t-lymphoblastic-leukemia-lymphoma.html index 0de5d41abd..0ccad58538 100644 --- a/concepts/t-lymphoblastic-leukemia-lymphoma.html +++ b/concepts/t-lymphoblastic-leukemia-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/tai-chi.html b/concepts/tai-chi.html index 6e21d9db6f..d9c901030a 100644 --- a/concepts/tai-chi.html +++ b/concepts/tai-chi.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/takeda.html b/concepts/takeda.html index 3aef4463f7..0aa3cc25d5 100644 --- a/concepts/takeda.html +++ b/concepts/takeda.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/tamoxifen.html b/concepts/tamoxifen.html index b6c440e9c9..7eea84b81d 100644 --- a/concepts/tamoxifen.html +++ b/concepts/tamoxifen.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/taxotere.html b/concepts/taxotere.html index 2fe5ac4b3e..d14688291c 100644 --- a/concepts/taxotere.html +++ b/concepts/taxotere.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/tcga.html b/concepts/tcga.html index b12c4556c0..a5a845f3de 100644 --- a/concepts/tcga.html +++ b/concepts/tcga.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/tcr-au.html b/concepts/tcr-au.html index 4f710cc772..8d843b8bec 100644 --- a/concepts/tcr-au.html +++ b/concepts/tcr-au.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/telangiectatic-osteosarcoma.html b/concepts/telangiectatic-osteosarcoma.html index 5ac095feaa..714d2dab04 100644 --- a/concepts/telangiectatic-osteosarcoma.html +++ b/concepts/telangiectatic-osteosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/temozolomide.html b/concepts/temozolomide.html index 6f59cbea73..c5be55ed5c 100644 --- a/concepts/temozolomide.html +++ b/concepts/temozolomide.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/teniposide.html b/concepts/teniposide.html index 35edc269a9..0219998a33 100644 --- a/concepts/teniposide.html +++ b/concepts/teniposide.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/tenosynovial-giant-cell-tumor-diffuse-type.html b/concepts/tenosynovial-giant-cell-tumor-diffuse-type.html index dbab6b75c2..ccfd4638c6 100644 --- a/concepts/tenosynovial-giant-cell-tumor-diffuse-type.html +++ b/concepts/tenosynovial-giant-cell-tumor-diffuse-type.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/teratoma.html b/concepts/teratoma.html index 7fda2afd4d..5f012f6c84 100644 --- a/concepts/teratoma.html +++ b/concepts/teratoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/tesetaxel.html b/concepts/tesetaxel.html index f1281204f2..dc54725a59 100644 --- a/concepts/tesetaxel.html +++ b/concepts/tesetaxel.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/testicular-lymphoma.html b/concepts/testicular-lymphoma.html index d759cd13e7..d8552ad88d 100644 --- a/concepts/testicular-lymphoma.html +++ b/concepts/testicular-lymphoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/testicular-mesothelioma.html b/concepts/testicular-mesothelioma.html index 7fe4278bd1..5778b4b3e0 100644 --- a/concepts/testicular-mesothelioma.html +++ b/concepts/testicular-mesothelioma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/testis.html b/concepts/testis.html index 713790dc41..fc06dd5f32 100644 --- a/concepts/testis.html +++ b/concepts/testis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/teva.html b/concepts/teva.html index d1e7df854e..52720adcd5 100644 --- a/concepts/teva.html +++ b/concepts/teva.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/the-barbara-ann-karmanos-cancer-institute.html b/concepts/the-barbara-ann-karmanos-cancer-institute.html index ced0687133..1623f54393 100644 --- a/concepts/the-barbara-ann-karmanos-cancer-institute.html +++ b/concepts/the-barbara-ann-karmanos-cancer-institute.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/the-biology-of-cancer.html b/concepts/the-biology-of-cancer.html index bb0d02b8da..f6201182b7 100644 --- a/concepts/the-biology-of-cancer.html +++ b/concepts/the-biology-of-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/the-bright-hour-a-memoir-of-living-and-dying.html b/concepts/the-bright-hour-a-memoir-of-living-and-dying.html index 93f9b5f77a..8aca83c1df 100644 --- a/concepts/the-bright-hour-a-memoir-of-living-and-dying.html +++ b/concepts/the-bright-hour-a-memoir-of-living-and-dying.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/the-c-word.html b/concepts/the-c-word.html index 81440b2b4a..f5cb77a63f 100644 --- a/concepts/the-c-word.html +++ b/concepts/the-c-word.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/the-cancer-journals.html b/concepts/the-cancer-journals.html index 2da453359c..f55a18a42c 100644 --- a/concepts/the-cancer-journals.html +++ b/concepts/the-cancer-journals.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/the-death-of-cancer-book.html b/concepts/the-death-of-cancer-book.html index 710668c4be..0fb6d32662 100644 --- a/concepts/the-death-of-cancer-book.html +++ b/concepts/the-death-of-cancer-book.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/the-dog-cancer-survival-guide.html b/concepts/the-dog-cancer-survival-guide.html index 0330d6ec6f..f2db93ef11 100644 --- a/concepts/the-dog-cancer-survival-guide.html +++ b/concepts/the-dog-cancer-survival-guide.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/the-eden-prescription-book.html b/concepts/the-eden-prescription-book.html index 8c0f673962..f5e6598992 100644 --- a/concepts/the-eden-prescription-book.html +++ b/concepts/the-eden-prescription-book.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/the-effects-of-herbs-and-fruits-on-leukaemia.html b/concepts/the-effects-of-herbs-and-fruits-on-leukaemia.html index d807500244..70931cfe7b 100644 --- a/concepts/the-effects-of-herbs-and-fruits-on-leukaemia.html +++ b/concepts/the-effects-of-herbs-and-fruits-on-leukaemia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/the-hope-foundation.html b/concepts/the-hope-foundation.html index 4f9c38e160..b2b5365b21 100644 --- a/concepts/the-hope-foundation.html +++ b/concepts/the-hope-foundation.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/the-hyve.html b/concepts/the-hyve.html index cd1eb77bfa..a83a744189 100644 --- a/concepts/the-hyve.html +++ b/concepts/the-hyve.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/the-immortal-life-of-henrietta-lacks.html b/concepts/the-immortal-life-of-henrietta-lacks.html index 285ac339f2..b56d6189ad 100644 --- a/concepts/the-immortal-life-of-henrietta-lacks.html +++ b/concepts/the-immortal-life-of-henrietta-lacks.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/the-jackson-laboratory-cancer-center.html b/concepts/the-jackson-laboratory-cancer-center.html index 1a164a52be..e2b5b3c6eb 100644 --- a/concepts/the-jackson-laboratory-cancer-center.html +++ b/concepts/the-jackson-laboratory-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/the-lancet-oncology.html b/concepts/the-lancet-oncology.html index ddc8fff28d..7d92c0cca3 100644 --- a/concepts/the-lancet-oncology.html +++ b/concepts/the-lancet-oncology.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/the-metabolic-approach-to-cancer.html b/concepts/the-metabolic-approach-to-cancer.html index ed5eba92d6..7cc4edc023 100644 --- a/concepts/the-metabolic-approach-to-cancer.html +++ b/concepts/the-metabolic-approach-to-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/the-ohio-state-university-comprehensive-cancer-center.html b/concepts/the-ohio-state-university-comprehensive-cancer-center.html index 2e44c6989a..8396ebdda7 100644 --- a/concepts/the-ohio-state-university-comprehensive-cancer-center.html +++ b/concepts/the-ohio-state-university-comprehensive-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/the-skin-cancer-foundation.html b/concepts/the-skin-cancer-foundation.html index d397e40564..dc662578a6 100644 --- a/concepts/the-skin-cancer-foundation.html +++ b/concepts/the-skin-cancer-foundation.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/the-truth-about-cancer.html b/concepts/the-truth-about-cancer.html index 8355847cb8..a1f314a1fb 100644 --- a/concepts/the-truth-about-cancer.html +++ b/concepts/the-truth-about-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/the-truth-in-small-doses-book.html b/concepts/the-truth-in-small-doses-book.html index 2e83197673..0e2f66ac05 100644 --- a/concepts/the-truth-in-small-doses-book.html +++ b/concepts/the-truth-in-small-doses-book.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/the-university-of-chicago-comprehensive-cancer-center.html b/concepts/the-university-of-chicago-comprehensive-cancer-center.html index 8fec62af8b..147edb406a 100644 --- a/concepts/the-university-of-chicago-comprehensive-cancer-center.html +++ b/concepts/the-university-of-chicago-comprehensive-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/the-university-of-kansas-cancer-center.html b/concepts/the-university-of-kansas-cancer-center.html index f4448cc397..f9649c3660 100644 --- a/concepts/the-university-of-kansas-cancer-center.html +++ b/concepts/the-university-of-kansas-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/the-wistar-institute-cancer-center.html b/concepts/the-wistar-institute-cancer-center.html index 232b47a706..08701d3eb0 100644 --- a/concepts/the-wistar-institute-cancer-center.html +++ b/concepts/the-wistar-institute-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/therapy-related-acute-myeloid-leukemia.html b/concepts/therapy-related-acute-myeloid-leukemia.html index 7397dbbb27..09f42cb1d3 100644 --- a/concepts/therapy-related-acute-myeloid-leukemia.html +++ b/concepts/therapy-related-acute-myeloid-leukemia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/therapy-related-myelodysplastic-syndrome.html b/concepts/therapy-related-myelodysplastic-syndrome.html index d2db53c963..0f39070c63 100644 --- a/concepts/therapy-related-myelodysplastic-syndrome.html +++ b/concepts/therapy-related-myelodysplastic-syndrome.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/therapy-related-myeloid-neoplasms.html b/concepts/therapy-related-myeloid-neoplasms.html index ef4ccfe3b3..dd84e81275 100644 --- a/concepts/therapy-related-myeloid-neoplasms.html +++ b/concepts/therapy-related-myeloid-neoplasms.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/thiotepa.html b/concepts/thiotepa.html index d8a962a375..3f838a88d6 100644 --- a/concepts/thiotepa.html +++ b/concepts/thiotepa.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/thymic-carcinoma.html b/concepts/thymic-carcinoma.html index d6076ff54f..16e8365c24 100644 --- a/concepts/thymic-carcinoma.html +++ b/concepts/thymic-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/thymic-epithelial-tumor.html b/concepts/thymic-epithelial-tumor.html index 6db22f5857..2f110a5240 100644 --- a/concepts/thymic-epithelial-tumor.html +++ b/concepts/thymic-epithelial-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/thymic-neuroendocrine-tumor.html b/concepts/thymic-neuroendocrine-tumor.html index 62edfc5d85..1933c58ad2 100644 --- a/concepts/thymic-neuroendocrine-tumor.html +++ b/concepts/thymic-neuroendocrine-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/thymoma.html b/concepts/thymoma.html index d4c9dd3e41..068f8552ec 100644 --- a/concepts/thymoma.html +++ b/concepts/thymoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/thymus.html b/concepts/thymus.html index f7f7eeb093..70d8efcd87 100644 --- a/concepts/thymus.html +++ b/concepts/thymus.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/thyroid-cancer-subreddit.html b/concepts/thyroid-cancer-subreddit.html index e3827d1f09..6593b6a71b 100644 --- a/concepts/thyroid-cancer-subreddit.html +++ b/concepts/thyroid-cancer-subreddit.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/thyroid.html b/concepts/thyroid.html index c0e4c8cdc6..55846fdb57 100644 --- a/concepts/thyroid.html +++ b/concepts/thyroid.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/thyroidectomy.html b/concepts/thyroidectomy.html index b8f1589b22..1eb59b73bd 100644 --- a/concepts/thyroidectomy.html +++ b/concepts/thyroidectomy.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/tioguanine.html b/concepts/tioguanine.html index 65fd25e005..f386b2636d 100644 --- a/concepts/tioguanine.html +++ b/concepts/tioguanine.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/tisch-cancer-institute.html b/concepts/tisch-cancer-institute.html index 1f48e6b7f4..57e7995828 100644 --- a/concepts/tisch-cancer-institute.html +++ b/concepts/tisch-cancer-institute.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/tlcr-dz.html b/concepts/tlcr-dz.html index a0b3aec514..3dc3346b50 100644 --- a/concepts/tlcr-dz.html +++ b/concepts/tlcr-dz.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/tocr-dz.html b/concepts/tocr-dz.html index 90d7ac03ef..22c1f0c5cf 100644 --- a/concepts/tocr-dz.html +++ b/concepts/tocr-dz.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/topical-medication.html b/concepts/topical-medication.html index 0f3b0fe187..5d17413230 100644 --- a/concepts/topical-medication.html +++ b/concepts/topical-medication.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/topotecan.html b/concepts/topotecan.html index 40bb014caa..8cda71aa1f 100644 --- a/concepts/topotecan.html +++ b/concepts/topotecan.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/toxin-avoidance.html b/concepts/toxin-avoidance.html index 54a5c939bd..6dc8ee51ae 100644 --- a/concepts/toxin-avoidance.html +++ b/concepts/toxin-avoidance.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/transarterial-chemoembolization.html b/concepts/transarterial-chemoembolization.html index e49477be99..5becbada51 100644 --- a/concepts/transarterial-chemoembolization.html +++ b/concepts/transarterial-chemoembolization.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/transarterial-embolization.html b/concepts/transarterial-embolization.html index e8564e1c59..19fb6a52df 100644 --- a/concepts/transarterial-embolization.html +++ b/concepts/transarterial-embolization.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/transient-abnormal-myelopoiesis.html b/concepts/transient-abnormal-myelopoiesis.html index f9f98d6fb8..1fdca4cdbd 100644 --- a/concepts/transient-abnormal-myelopoiesis.html +++ b/concepts/transient-abnormal-myelopoiesis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/translocation-associated-renal-cell-carcinoma.html b/concepts/translocation-associated-renal-cell-carcinoma.html index 615d2afdc4..45ad47f340 100644 --- a/concepts/translocation-associated-renal-cell-carcinoma.html +++ b/concepts/translocation-associated-renal-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/treosulfan.html b/concepts/treosulfan.html index df45599b3a..79fc37af59 100644 --- a/concepts/treosulfan.html +++ b/concepts/treosulfan.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/tretinoin.html b/concepts/tretinoin.html index 52b05c6ee0..b526f5d68f 100644 --- a/concepts/tretinoin.html +++ b/concepts/tretinoin.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/triaziquone.html b/concepts/triaziquone.html index 1b65ba6c69..a457dfc5de 100644 --- a/concepts/triaziquone.html +++ b/concepts/triaziquone.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/triethylenemelamine.html b/concepts/triethylenemelamine.html index 82e240e924..37bb5e22f2 100644 --- a/concepts/triethylenemelamine.html +++ b/concepts/triethylenemelamine.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/trofosfamide.html b/concepts/trofosfamide.html index afde14f3b7..f1269d5772 100644 --- a/concepts/trofosfamide.html +++ b/concepts/trofosfamide.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/tubular-adenoma-of-the-colon.html b/concepts/tubular-adenoma-of-the-colon.html index 8c19242e0d..19d2ac3991 100644 --- a/concepts/tubular-adenoma-of-the-colon.html +++ b/concepts/tubular-adenoma-of-the-colon.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/tubular-stomach-adenocarcinoma.html b/concepts/tubular-stomach-adenocarcinoma.html index 79426332c5..2b6bc7d200 100644 --- a/concepts/tubular-stomach-adenocarcinoma.html +++ b/concepts/tubular-stomach-adenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/uc-davis-comprehensive-cancer-center.html b/concepts/uc-davis-comprehensive-cancer-center.html index a929ae2a04..2b16000ee3 100644 --- a/concepts/uc-davis-comprehensive-cancer-center.html +++ b/concepts/uc-davis-comprehensive-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/uccc.html b/concepts/uccc.html index b11db60201..a426b57d2b 100644 --- a/concepts/uccc.html +++ b/concepts/uccc.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ucsf-helen-diller-family-comprehensive-cancer-center.html b/concepts/ucsf-helen-diller-family-comprehensive-cancer-center.html index fe3a9e3d5e..e3c5461ed0 100644 --- a/concepts/ucsf-helen-diller-family-comprehensive-cancer-center.html +++ b/concepts/ucsf-helen-diller-family-comprehensive-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/ue-lifesciences.html b/concepts/ue-lifesciences.html index 18c3d95384..9612a5318d 100644 --- a/concepts/ue-lifesciences.html +++ b/concepts/ue-lifesciences.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/uicc.html b/concepts/uicc.html index 9de666bc07..b3f11e2970 100644 --- a/concepts/uicc.html +++ b/concepts/uicc.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/umls.html b/concepts/umls.html index a7599df0f7..5546f3345c 100644 --- a/concepts/umls.html +++ b/concepts/umls.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/un.html b/concepts/un.html index a84963a6c0..cb8c6ecb52 100644 --- a/concepts/un.html +++ b/concepts/un.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/unc-lineberger-comprehensive-cancer-center.html b/concepts/unc-lineberger-comprehensive-cancer-center.html index 0ddc6a01b0..5b315aef19 100644 --- a/concepts/unc-lineberger-comprehensive-cancer-center.html +++ b/concepts/unc-lineberger-comprehensive-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/unclassified-renal-cell-carcinoma.html b/concepts/unclassified-renal-cell-carcinoma.html index 9adc4b0350..3b02795736 100644 --- a/concepts/unclassified-renal-cell-carcinoma.html +++ b/concepts/unclassified-renal-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/undifferentiated-carcinoma-of-the-pancreas.html b/concepts/undifferentiated-carcinoma-of-the-pancreas.html index c35d5e783f..c7594b1a07 100644 --- a/concepts/undifferentiated-carcinoma-of-the-pancreas.html +++ b/concepts/undifferentiated-carcinoma-of-the-pancreas.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/undifferentiated-embryonal-sarcoma-of-the-liver.html b/concepts/undifferentiated-embryonal-sarcoma-of-the-liver.html index fd827891b4..616b2f2646 100644 --- a/concepts/undifferentiated-embryonal-sarcoma-of-the-liver.html +++ b/concepts/undifferentiated-embryonal-sarcoma-of-the-liver.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/undifferentiated-malignant-neoplasm.html b/concepts/undifferentiated-malignant-neoplasm.html index 113fabf72d..f95e76941e 100644 --- a/concepts/undifferentiated-malignant-neoplasm.html +++ b/concepts/undifferentiated-malignant-neoplasm.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/undifferentiated-pleomorphic-sarcoma-malignant-fibrous-histiocytoma-high-grade-spindle-cell-sarcoma.html b/concepts/undifferentiated-pleomorphic-sarcoma-malignant-fibrous-histiocytoma-high-grade-spindle-cell-sarcoma.html index 15eb78bcfc..0c38f81a68 100644 --- a/concepts/undifferentiated-pleomorphic-sarcoma-malignant-fibrous-histiocytoma-high-grade-spindle-cell-sarcoma.html +++ b/concepts/undifferentiated-pleomorphic-sarcoma-malignant-fibrous-histiocytoma-high-grade-spindle-cell-sarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/undifferentiated-stomach-adenocarcinoma.html b/concepts/undifferentiated-stomach-adenocarcinoma.html index 55cac2dab6..dde682118f 100644 --- a/concepts/undifferentiated-stomach-adenocarcinoma.html +++ b/concepts/undifferentiated-stomach-adenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/undifferentiated-uterine-sarcoma.html b/concepts/undifferentiated-uterine-sarcoma.html index c546a3d18f..f508553b22 100644 --- a/concepts/undifferentiated-uterine-sarcoma.html +++ b/concepts/undifferentiated-uterine-sarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/unicancer.html b/concepts/unicancer.html index 7fb148492b..be9e59b6d7 100644 --- a/concepts/unicancer.html +++ b/concepts/unicancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/university-of-colorado-cancer-center.html b/concepts/university-of-colorado-cancer-center.html index bac664b37a..18153abf93 100644 --- a/concepts/university-of-colorado-cancer-center.html +++ b/concepts/university-of-colorado-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/university-of-hawaii-cancer-center.html b/concepts/university-of-hawaii-cancer-center.html index c97b04c4bf..cfed7ba5ec 100644 --- a/concepts/university-of-hawaii-cancer-center.html +++ b/concepts/university-of-hawaii-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/university-of-maryland-marlene-and-stewart-greenebaum-comprehensive-cancer-center.html b/concepts/university-of-maryland-marlene-and-stewart-greenebaum-comprehensive-cancer-center.html index 6d401ec2d4..874252568c 100644 --- a/concepts/university-of-maryland-marlene-and-stewart-greenebaum-comprehensive-cancer-center.html +++ b/concepts/university-of-maryland-marlene-and-stewart-greenebaum-comprehensive-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/university-of-michigan-rogel-cancer-center.html b/concepts/university-of-michigan-rogel-cancer-center.html index 6266981ae1..29108e4fe9 100644 --- a/concepts/university-of-michigan-rogel-cancer-center.html +++ b/concepts/university-of-michigan-rogel-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/university-of-new-mexico-cancer-research-and-treatment-center.html b/concepts/university-of-new-mexico-cancer-research-and-treatment-center.html index ffe87276be..abc94eec19 100644 --- a/concepts/university-of-new-mexico-cancer-research-and-treatment-center.html +++ b/concepts/university-of-new-mexico-cancer-research-and-treatment-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/university-of-virginia-cancer-center.html b/concepts/university-of-virginia-cancer-center.html index b1faab7c02..0a0e2ea289 100644 --- a/concepts/university-of-virginia-cancer-center.html +++ b/concepts/university-of-virginia-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/university-of-wisconsin-carbone-cancer-center.html b/concepts/university-of-wisconsin-carbone-cancer-center.html index a3d6188a45..84d4fca471 100644 --- a/concepts/university-of-wisconsin-carbone-cancer-center.html +++ b/concepts/university-of-wisconsin-carbone-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/upmc-hillman-cancer-center.html b/concepts/upmc-hillman-cancer-center.html index 25088fb9a4..75d27fc339 100644 --- a/concepts/upmc-hillman-cancer-center.html +++ b/concepts/upmc-hillman-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/upper-tract-urothelial-carcinoma.html b/concepts/upper-tract-urothelial-carcinoma.html index b35130618c..cb589aaad2 100644 --- a/concepts/upper-tract-urothelial-carcinoma.html +++ b/concepts/upper-tract-urothelial-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/urachal-adenocarcinoma.html b/concepts/urachal-adenocarcinoma.html index 91cbd5be44..e70374eda4 100644 --- a/concepts/urachal-adenocarcinoma.html +++ b/concepts/urachal-adenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/urachal-carcinoma.html b/concepts/urachal-carcinoma.html index f2e7d20c97..2469442205 100644 --- a/concepts/urachal-carcinoma.html +++ b/concepts/urachal-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/uramustine.html b/concepts/uramustine.html index 1cf8c67285..db12bb1a30 100644 --- a/concepts/uramustine.html +++ b/concepts/uramustine.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/urethral-adenocarcinoma.html b/concepts/urethral-adenocarcinoma.html index b8c07c3f10..2b84bd55cd 100644 --- a/concepts/urethral-adenocarcinoma.html +++ b/concepts/urethral-adenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/urethral-cancer.html b/concepts/urethral-cancer.html index 83d5a28da8..25fd6d7b3f 100644 --- a/concepts/urethral-cancer.html +++ b/concepts/urethral-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/urethral-squamous-cell-carcinoma.html b/concepts/urethral-squamous-cell-carcinoma.html index 95853ef9f3..5c1eb6784a 100644 --- a/concepts/urethral-squamous-cell-carcinoma.html +++ b/concepts/urethral-squamous-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/urethral-urothelial-carcinoma.html b/concepts/urethral-urothelial-carcinoma.html index 09dec65b62..460b612d72 100644 --- a/concepts/urethral-urothelial-carcinoma.html +++ b/concepts/urethral-urothelial-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/urothelial-papilloma.html b/concepts/urothelial-papilloma.html index 46d21d4f18..7046b9ca56 100644 --- a/concepts/urothelial-papilloma.html +++ b/concepts/urothelial-papilloma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/usc-norris-comprehensive-cancer-center.html b/concepts/usc-norris-comprehensive-cancer-center.html index 8079b776e8..aa502ee4d5 100644 --- a/concepts/usc-norris-comprehensive-cancer-center.html +++ b/concepts/usc-norris-comprehensive-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/uscs.html b/concepts/uscs.html index 077251cf22..759c93fcf6 100644 --- a/concepts/uscs.html +++ b/concepts/uscs.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/uterine-adenosarcoma.html b/concepts/uterine-adenosarcoma.html index 9462ae02de..bed9429b88 100644 --- a/concepts/uterine-adenosarcoma.html +++ b/concepts/uterine-adenosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/uterine-adenosquamous-carcinoma.html b/concepts/uterine-adenosquamous-carcinoma.html index 45a3466c85..393038f545 100644 --- a/concepts/uterine-adenosquamous-carcinoma.html +++ b/concepts/uterine-adenosquamous-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/uterine-carcinosarcoma-uterine-malignant-mixed-mullerian-tumor.html b/concepts/uterine-carcinosarcoma-uterine-malignant-mixed-mullerian-tumor.html index d0d4fe869b..9d650dc379 100644 --- a/concepts/uterine-carcinosarcoma-uterine-malignant-mixed-mullerian-tumor.html +++ b/concepts/uterine-carcinosarcoma-uterine-malignant-mixed-mullerian-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/uterine-clear-cell-carcinoma.html b/concepts/uterine-clear-cell-carcinoma.html index f51fc6ce37..b52c9d7c5c 100644 --- a/concepts/uterine-clear-cell-carcinoma.html +++ b/concepts/uterine-clear-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/uterine-dedifferentiated-carcinoma.html b/concepts/uterine-dedifferentiated-carcinoma.html index c0c0bd4cf4..6fa85ed856 100644 --- a/concepts/uterine-dedifferentiated-carcinoma.html +++ b/concepts/uterine-dedifferentiated-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/uterine-endometrioid-carcinoma.html b/concepts/uterine-endometrioid-carcinoma.html index ba9d7dc115..d8f604e701 100644 --- a/concepts/uterine-endometrioid-carcinoma.html +++ b/concepts/uterine-endometrioid-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/uterine-epithelioid-leiomyosarcoma.html b/concepts/uterine-epithelioid-leiomyosarcoma.html index 187d94a3f9..86e52c9ab4 100644 --- a/concepts/uterine-epithelioid-leiomyosarcoma.html +++ b/concepts/uterine-epithelioid-leiomyosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/uterine-leiomyoma.html b/concepts/uterine-leiomyoma.html index e1d80f4863..159b1e9edd 100644 --- a/concepts/uterine-leiomyoma.html +++ b/concepts/uterine-leiomyoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/uterine-leiomyosarcoma.html b/concepts/uterine-leiomyosarcoma.html index 2eb0df6882..5f2de58dfb 100644 --- a/concepts/uterine-leiomyosarcoma.html +++ b/concepts/uterine-leiomyosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/uterine-mesonephric-carcinoma.html b/concepts/uterine-mesonephric-carcinoma.html index e5421566f4..3a608c9ecf 100644 --- a/concepts/uterine-mesonephric-carcinoma.html +++ b/concepts/uterine-mesonephric-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/uterine-mixed-endometrial-carcinoma.html b/concepts/uterine-mixed-endometrial-carcinoma.html index e20512ef63..d55048c724 100644 --- a/concepts/uterine-mixed-endometrial-carcinoma.html +++ b/concepts/uterine-mixed-endometrial-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/uterine-mucinous-carcinoma.html b/concepts/uterine-mucinous-carcinoma.html index 9040aa13e6..083b2bb8ad 100644 --- a/concepts/uterine-mucinous-carcinoma.html +++ b/concepts/uterine-mucinous-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/uterine-myxoid-leiomyosarcoma.html b/concepts/uterine-myxoid-leiomyosarcoma.html index c3e851cc52..0577d9354e 100644 --- a/concepts/uterine-myxoid-leiomyosarcoma.html +++ b/concepts/uterine-myxoid-leiomyosarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/uterine-neuroendocrine-carcinoma.html b/concepts/uterine-neuroendocrine-carcinoma.html index 4617932b4d..fdb66cc36a 100644 --- a/concepts/uterine-neuroendocrine-carcinoma.html +++ b/concepts/uterine-neuroendocrine-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/uterine-perivascular-epithelioid-cell-tumor.html b/concepts/uterine-perivascular-epithelioid-cell-tumor.html index 8fa89bd030..bd8f3e7d00 100644 --- a/concepts/uterine-perivascular-epithelioid-cell-tumor.html +++ b/concepts/uterine-perivascular-epithelioid-cell-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/uterine-sarcoma-mesenchymal.html b/concepts/uterine-sarcoma-mesenchymal.html index f931cc08ef..fcc70acdad 100644 --- a/concepts/uterine-sarcoma-mesenchymal.html +++ b/concepts/uterine-sarcoma-mesenchymal.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/uterine-sarcoma-other.html b/concepts/uterine-sarcoma-other.html index 118d539fdb..c8245a48ad 100644 --- a/concepts/uterine-sarcoma-other.html +++ b/concepts/uterine-sarcoma-other.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/uterine-serous-carcinoma-uterine-papillary-serous-carcinoma.html b/concepts/uterine-serous-carcinoma-uterine-papillary-serous-carcinoma.html index 4a47f0ce5a..a9ea3e5071 100644 --- a/concepts/uterine-serous-carcinoma-uterine-papillary-serous-carcinoma.html +++ b/concepts/uterine-serous-carcinoma-uterine-papillary-serous-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/uterine-smooth-muscle-tumor-of-uncertain-malignant-potential.html b/concepts/uterine-smooth-muscle-tumor-of-uncertain-malignant-potential.html index 46a6cefcdc..414c98b999 100644 --- a/concepts/uterine-smooth-muscle-tumor-of-uncertain-malignant-potential.html +++ b/concepts/uterine-smooth-muscle-tumor-of-uncertain-malignant-potential.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/uterine-smooth-muscle-tumor.html b/concepts/uterine-smooth-muscle-tumor.html index f944f358a6..8eb0c8a265 100644 --- a/concepts/uterine-smooth-muscle-tumor.html +++ b/concepts/uterine-smooth-muscle-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/uterine-undifferentiated-carcinoma.html b/concepts/uterine-undifferentiated-carcinoma.html index 7b1dd1e041..90f4bbecd6 100644 --- a/concepts/uterine-undifferentiated-carcinoma.html +++ b/concepts/uterine-undifferentiated-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/uterus.html b/concepts/uterus.html index 44f898047f..0e1a4d418d 100644 --- a/concepts/uterus.html +++ b/concepts/uterus.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/uveal-melanoma.html b/concepts/uveal-melanoma.html index 0e87ba5a6d..02263d4169 100644 --- a/concepts/uveal-melanoma.html +++ b/concepts/uveal-melanoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/vacf.html b/concepts/vacf.html index ad0dd2a580..a54b0f90c4 100644 --- a/concepts/vacf.html +++ b/concepts/vacf.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/vaginal-adenocarcinoma.html b/concepts/vaginal-adenocarcinoma.html index af4372c126..c151360631 100644 --- a/concepts/vaginal-adenocarcinoma.html +++ b/concepts/vaginal-adenocarcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/valrubicin.html b/concepts/valrubicin.html index 4279e3e055..bb69d671fc 100644 --- a/concepts/valrubicin.html +++ b/concepts/valrubicin.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/vanderbilt-ingram-cancer-center.html b/concepts/vanderbilt-ingram-cancer-center.html index eb88a9fccb..8072508861 100644 --- a/concepts/vanderbilt-ingram-cancer-center.html +++ b/concepts/vanderbilt-ingram-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/vats.html b/concepts/vats.html index e8596e5f44..7baf725380 100644 --- a/concepts/vats.html +++ b/concepts/vats.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/vcr-au.html b/concepts/vcr-au.html index 4b24ece719..9b1f4b149e 100644 --- a/concepts/vcr-au.html +++ b/concepts/vcr-au.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/veganism.html b/concepts/veganism.html index bfb099719c..1d18e8ca86 100644 --- a/concepts/veganism.html +++ b/concepts/veganism.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/vegetarianism.html b/concepts/vegetarianism.html index abccd5bf53..a61c622e60 100644 --- a/concepts/vegetarianism.html +++ b/concepts/vegetarianism.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/vemurafenib.html b/concepts/vemurafenib.html index d82ffc2f28..f7cf7197ba 100644 --- a/concepts/vemurafenib.html +++ b/concepts/vemurafenib.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/verrucous-penile-squamous-cell-carcinoma.html b/concepts/verrucous-penile-squamous-cell-carcinoma.html index 49a8e97c7f..d01415807e 100644 --- a/concepts/verrucous-penile-squamous-cell-carcinoma.html +++ b/concepts/verrucous-penile-squamous-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/viatris.html b/concepts/viatris.html index 7e41516911..ba63269857 100644 --- a/concepts/viatris.html +++ b/concepts/viatris.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/villoglandular-adenocarcinoma-of-the-cervix.html b/concepts/villoglandular-adenocarcinoma-of-the-cervix.html index ab8196af37..2c27b01c09 100644 --- a/concepts/villoglandular-adenocarcinoma-of-the-cervix.html +++ b/concepts/villoglandular-adenocarcinoma-of-the-cervix.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/villoglandular-carcinoma.html b/concepts/villoglandular-carcinoma.html index 55967b14e5..66ce7392cf 100644 --- a/concepts/villoglandular-carcinoma.html +++ b/concepts/villoglandular-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/vinblastine.html b/concepts/vinblastine.html index 53d7fb1550..a84518ee1d 100644 --- a/concepts/vinblastine.html +++ b/concepts/vinblastine.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/vincristine.html b/concepts/vincristine.html index 329ea968e6..40b5866b1b 100644 --- a/concepts/vincristine.html +++ b/concepts/vincristine.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/vindesine.html b/concepts/vindesine.html index ca4791c9fe..e08d455c62 100644 --- a/concepts/vindesine.html +++ b/concepts/vindesine.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/vinorelbine.html b/concepts/vinorelbine.html index 1e5e91ac61..1bdbc1f7ac 100644 --- a/concepts/vinorelbine.html +++ b/concepts/vinorelbine.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/vismodegib.html b/concepts/vismodegib.html index 0f9e3c33dc..431015f4b7 100644 --- a/concepts/vismodegib.html +++ b/concepts/vismodegib.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/vitamin-a.html b/concepts/vitamin-a.html index d6228dd7fc..b90fd41430 100644 --- a/concepts/vitamin-a.html +++ b/concepts/vitamin-a.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/vitamin-b.html b/concepts/vitamin-b.html index 5ced2e87a4..1c59f5f0c8 100644 --- a/concepts/vitamin-b.html +++ b/concepts/vitamin-b.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/vitamin-c.html b/concepts/vitamin-c.html index a68b0f8b79..7974168c3f 100644 --- a/concepts/vitamin-c.html +++ b/concepts/vitamin-c.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/vitamin-d.html b/concepts/vitamin-d.html index 117d94ead6..0f0adbbe50 100644 --- a/concepts/vitamin-d.html +++ b/concepts/vitamin-d.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/vorinostat.html b/concepts/vorinostat.html index 363929515f..e7986565bb 100644 --- a/concepts/vorinostat.html +++ b/concepts/vorinostat.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/vulva-vagina.html b/concepts/vulva-vagina.html index 1b83a2993c..69ac0ba1de 100644 --- a/concepts/vulva-vagina.html +++ b/concepts/vulva-vagina.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/wacanreg.html b/concepts/wacanreg.html index d7e3476bdb..dedc0d825c 100644 --- a/concepts/wacanreg.html +++ b/concepts/wacanreg.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/wake-forest-baptist-comprehensive-cancer-center.html b/concepts/wake-forest-baptist-comprehensive-cancer-center.html index 8b67a2de0f..c956b8a269 100644 --- a/concepts/wake-forest-baptist-comprehensive-cancer-center.html +++ b/concepts/wake-forest-baptist-comprehensive-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/waldenstrom-macroglobulinemia.html b/concepts/waldenstrom-macroglobulinemia.html index a150465613..b2a9cb0908 100644 --- a/concepts/waldenstrom-macroglobulinemia.html +++ b/concepts/waldenstrom-macroglobulinemia.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/warty-penile-squamous-cell-carcinoma.html b/concepts/warty-penile-squamous-cell-carcinoma.html index 8befaf1fde..36695396bf 100644 --- a/concepts/warty-penile-squamous-cell-carcinoma.html +++ b/concepts/warty-penile-squamous-cell-carcinoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/wcrf-uk.html b/concepts/wcrf-uk.html index bf74d84f60..5095dc3605 100644 --- a/concepts/wcrf-uk.html +++ b/concepts/wcrf-uk.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/wcrf.html b/concepts/wcrf.html index 449437ef44..0c6a5e0d17 100644 --- a/concepts/wcrf.html +++ b/concepts/wcrf.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/weight-loss.html b/concepts/weight-loss.html index d81ca76aab..8a8b11f6ca 100644 --- a/concepts/weight-loss.html +++ b/concepts/weight-loss.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/well-differentiated-liposarcoma.html b/concepts/well-differentiated-liposarcoma.html index 41b467070f..e6b14325be 100644 --- a/concepts/well-differentiated-liposarcoma.html +++ b/concepts/well-differentiated-liposarcoma.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/well-differentiated-neuroendocrine-tumor-of-the-appendix.html b/concepts/well-differentiated-neuroendocrine-tumor-of-the-appendix.html index c8e1b72531..539ec03ff8 100644 --- a/concepts/well-differentiated-neuroendocrine-tumor-of-the-appendix.html +++ b/concepts/well-differentiated-neuroendocrine-tumor-of-the-appendix.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/well-differentiated-neuroendocrine-tumor-of-the-rectum.html b/concepts/well-differentiated-neuroendocrine-tumor-of-the-rectum.html index 53a58dfe4a..a05ad86a76 100644 --- a/concepts/well-differentiated-neuroendocrine-tumor-of-the-rectum.html +++ b/concepts/well-differentiated-neuroendocrine-tumor-of-the-rectum.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/well-differentiated-neuroendocrine-tumors-of-the-stomach.html b/concepts/well-differentiated-neuroendocrine-tumors-of-the-stomach.html index 2a920284c0..0b95f4f86c 100644 --- a/concepts/well-differentiated-neuroendocrine-tumors-of-the-stomach.html +++ b/concepts/well-differentiated-neuroendocrine-tumors-of-the-stomach.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/well-differentiated-thyroid-cancer.html b/concepts/well-differentiated-thyroid-cancer.html index 56894b9364..429e06cc7c 100644 --- a/concepts/well-differentiated-thyroid-cancer.html +++ b/concepts/well-differentiated-thyroid-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/when-breath-becomes-air.html b/concepts/when-breath-becomes-air.html index e9fbb7f3d9..f700817a1c 100644 --- a/concepts/when-breath-becomes-air.html +++ b/concepts/when-breath-becomes-air.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/who.html b/concepts/who.html index f8e5ebcdc7..3de6d49cfd 100644 --- a/concepts/who.html +++ b/concepts/who.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/wilms-tumor.html b/concepts/wilms-tumor.html index 9d05fb9246..001af32842 100644 --- a/concepts/wilms-tumor.html +++ b/concepts/wilms-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/wim-hof-method.html b/concepts/wim-hof-method.html index c33c329bff..5c2b5ccee9 100644 --- a/concepts/wim-hof-method.html +++ b/concepts/wim-hof-method.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/winning-the-war-on-cancer-the-epic-journey-towards-a-natural-cure-book.html b/concepts/winning-the-war-on-cancer-the-epic-journey-towards-a-natural-cure-book.html index dd58095f85..888be5dfa2 100644 --- a/concepts/winning-the-war-on-cancer-the-epic-journey-towards-a-natural-cure-book.html +++ b/concepts/winning-the-war-on-cancer-the-epic-journey-towards-a-natural-cure-book.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/winship-cancer-institute.html b/concepts/winship-cancer-institute.html index 0fe2a880b5..673c6e1494 100644 --- a/concepts/winship-cancer-institute.html +++ b/concepts/winship-cancer-institute.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/wkof.html b/concepts/wkof.html index 136a23b36d..f42d59dee2 100644 --- a/concepts/wkof.html +++ b/concepts/wkof.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/world-cancer-day.html b/concepts/world-cancer-day.html index cfe6037235..f533037ec7 100644 --- a/concepts/world-cancer-day.html +++ b/concepts/world-cancer-day.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/world-lymphoma-awareness-day.html b/concepts/world-lymphoma-awareness-day.html index 2dae48f633..616f3446c6 100644 --- a/concepts/world-lymphoma-awareness-day.html +++ b/concepts/world-lymphoma-awareness-day.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/yale-cancer-center.html b/concepts/yale-cancer-center.html index 89b277f097..c1debaa3d4 100644 --- a/concepts/yale-cancer-center.html +++ b/concepts/yale-cancer-center.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/yale-introduction-to-breast-cancer.html b/concepts/yale-introduction-to-breast-cancer.html index d86decaa0c..fae0e61fff 100644 --- a/concepts/yale-introduction-to-breast-cancer.html +++ b/concepts/yale-introduction-to-breast-cancer.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/yoga.html b/concepts/yoga.html index 4c88260975..f91ee61a8c 100644 --- a/concepts/yoga.html +++ b/concepts/yoga.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/yolk-sac-tumor-cns-brain.html b/concepts/yolk-sac-tumor-cns-brain.html index eaac5253cb..c90466b663 100644 --- a/concepts/yolk-sac-tumor-cns-brain.html +++ b/concepts/yolk-sac-tumor-cns-brain.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/yolk-sac-tumor-testis.html b/concepts/yolk-sac-tumor-testis.html index 4b7d527079..18d16e43ad 100644 --- a/concepts/yolk-sac-tumor-testis.html +++ b/concepts/yolk-sac-tumor-testis.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/yolk-sac-tumor-vulva-vagina.html b/concepts/yolk-sac-tumor-vulva-vagina.html index d62c535230..f7b0cfde86 100644 --- a/concepts/yolk-sac-tumor-vulva-vagina.html +++ b/concepts/yolk-sac-tumor-vulva-vagina.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/yolk-sac-tumor.html b/concepts/yolk-sac-tumor.html index fecf0af9d2..59f0c645e0 100644 --- a/concepts/yolk-sac-tumor.html +++ b/concepts/yolk-sac-tumor.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/your-disease-risk.html b/concepts/your-disease-risk.html index 0aa6469443..861292a28c 100644 --- a/concepts/your-disease-risk.html +++ b/concepts/your-disease-risk.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/zmncr-zm.html b/concepts/zmncr-zm.html index 9f12ed7510..542a12a101 100644 --- a/concepts/zmncr-zm.html +++ b/concepts/zmncr-zm.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/zncr-zw.html b/concepts/zncr-zw.html index c8e57ce630..de74956897 100644 --- a/concepts/zncr-zw.html +++ b/concepts/zncr-zw.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/concepts/zorubicin.html b/concepts/zorubicin.html index 6d9cb645c6..8cc402be23 100644 --- a/concepts/zorubicin.html +++ b/concepts/zorubicin.html @@ -12,12 +12,12 @@ undefined - + - + diff --git a/csv.html b/csv.html index c336652913..0f071c6978 100644 --- a/csv.html +++ b/csv.html @@ -3,12 +3,12 @@ CancerDB.CSV - + - + diff --git a/download.html b/download.html index 34d9d396cc..57ba9523b1 100644 --- a/download.html +++ b/download.html @@ -3,12 +3,12 @@ Download CancerDB - + - + diff --git a/index.html b/index.html index c1413908f7..add8d8ae87 100644 --- a/index.html +++ b/index.html @@ -3,12 +3,12 @@ CancerDB - + - + diff --git a/list.html b/list.html index 7555479579..86cac12caa 100644 --- a/list.html +++ b/list.html @@ -3,12 +3,12 @@ CancerDB - All Concepts - + - + diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json index 5bc76dd7dd..bc9fe38cca 100644 --- a/node_modules/.package-lock.json +++ b/node_modules/.package-lock.json @@ -406,9 +406,9 @@ "dev": true }, "node_modules/scroll-cli": { - "version": "126.0.1", - "resolved": "https://registry.npmjs.org/scroll-cli/-/scroll-cli-126.0.1.tgz", - "integrity": "sha512-AEwozXRNPV4qfXZWlcozIMoh7kwSP7i1qXgEZvlGya6nGJ32bSxZ1lGjxxCI6zRmwJ0Esfkoz9cdGDx8thLPrQ==", + "version": "126.1.0", + "resolved": "https://registry.npmjs.org/scroll-cli/-/scroll-cli-126.1.0.tgz", + "integrity": "sha512-l+k7BmlTk54jqALevlM9bHFSOogWXPe+p5wColKZFOsSoJsk9jdSBCKAwnG8OQpMOldmCmzRUy6lVEWRlVkyNQ==", "dev": true, "dependencies": { "d3": "^6.7.0", diff --git a/node_modules/scroll-cli/external/codeMirror.css b/node_modules/scroll-cli/external/codeMirror.css new file mode 100644 index 0000000000..7b2475025d --- /dev/null +++ b/node_modules/scroll-cli/external/codeMirror.css @@ -0,0 +1,521 @@ +/* BASICS */ + +.CodeMirror { + /* Set height, width, borders, and global font properties here */ + font-family: monospace; + height: 300px; + color: black; + direction: ltr; +} + +/* PADDING */ + +.CodeMirror-lines { + padding: 4px 0; /* Vertical padding around content */ +} +.CodeMirror pre { + padding: 0 4px; /* Horizontal padding of content */ +} + +.CodeMirror-scrollbar-filler, +.CodeMirror-gutter-filler { + background-color: white; /* The little square between H and V scrollbars */ +} + +/* GUTTER */ + +.CodeMirror-gutters { + border-right: 1px solid #ddd; + background-color: #f7f7f7; + white-space: nowrap; +} +.CodeMirror-linenumbers { +} +.CodeMirror-linenumber { + padding: 0 3px 0 5px; + min-width: 20px; + text-align: right; + color: #999; + white-space: nowrap; +} + +.CodeMirror-guttermarker { + color: black; +} +.CodeMirror-guttermarker-subtle { + color: #999; +} + +/* CURSOR */ + +.CodeMirror-cursor { + border-left: 1px solid black; + border-right: none; + width: 0; +} +/* Shown when moving in bi-directional text */ +.CodeMirror div.CodeMirror-secondarycursor { + border-left: 1px solid silver; +} +.cm-fat-cursor .CodeMirror-cursor { + width: auto; + border: 0 !important; + background: #7e7; +} +.cm-fat-cursor div.CodeMirror-cursors { + z-index: 1; +} +.cm-fat-cursor-mark { + background-color: rgba(20, 255, 20, 0.5); + -webkit-animation: blink 1.06s steps(1) infinite; + -moz-animation: blink 1.06s steps(1) infinite; + animation: blink 1.06s steps(1) infinite; +} +.cm-animate-fat-cursor { + width: auto; + border: 0; + -webkit-animation: blink 1.06s steps(1) infinite; + -moz-animation: blink 1.06s steps(1) infinite; + animation: blink 1.06s steps(1) infinite; + background-color: #7e7; +} +@-moz-keyframes blink { + 0% { + } + 50% { + background-color: transparent; + } + 100% { + } +} +@-webkit-keyframes blink { + 0% { + } + 50% { + background-color: transparent; + } + 100% { + } +} +@keyframes blink { + 0% { + } + 50% { + background-color: transparent; + } + 100% { + } +} + +/* Can style cursor different in overwrite (non-insert) mode */ +.CodeMirror-overwrite .CodeMirror-cursor { +} + +.cm-tab { + display: inline-block; + text-decoration: inherit; +} + +.CodeMirror-rulers { + position: absolute; + left: 0; + right: 0; + top: -50px; + bottom: -20px; + overflow: hidden; +} +.CodeMirror-ruler { + border-left: 1px solid #ccc; + top: 0; + bottom: 0; + position: absolute; +} + +/* DEFAULT THEME */ + +.cm-s-default .cm-header { + color: blue; +} +.cm-s-default .cm-quote { + color: #090; +} +.cm-negative { + color: #d44; +} +.cm-positive { + color: #292; +} +.cm-header, +.cm-strong { + font-weight: bold; +} +.cm-em { + font-style: italic; +} +.cm-link { + text-decoration: underline; +} +.cm-strikethrough { + text-decoration: line-through; +} + +.cm-s-default .cm-keyword { + color: #708; +} +.cm-s-default .cm-atom { + color: #219; +} +.cm-s-default .cm-number { + color: #164; +} +.cm-s-default .cm-def { + color: #00f; +} +.cm-s-default .cm-variable, +.cm-s-default .cm-punctuation, +.cm-s-default .cm-property, +.cm-s-default .cm-operator { +} +.cm-s-default .cm-variable-2 { + color: #05a; +} +.cm-s-default .cm-variable-3, +.cm-s-default .cm-type { + color: #085; +} +.cm-s-default .cm-comment { + color: #a50; +} +.cm-s-default .cm-string { + color: #a11; +} +.cm-s-default .cm-string-2 { + color: #f50; +} +.cm-s-default .cm-meta { + color: #555; +} +.cm-s-default .cm-qualifier { + color: #555; +} +.cm-s-default .cm-builtin { + color: #30a; +} +.cm-s-default .cm-bracket { + color: #997; +} +.cm-s-default .cm-tag { + color: #170; +} +.cm-s-default .cm-attribute { + color: #00c; +} +.cm-s-default .cm-hr { + color: #999; +} +.cm-s-default .cm-link { + color: #00c; +} + +.cm-s-default .cm-error { + color: #f00; +} +.cm-invalidchar { + color: #f00; +} + +.CodeMirror-composing { + border-bottom: 2px solid; +} + +/* Default styles for common addons */ + +div.CodeMirror span.CodeMirror-matchingbracket { + color: #0b0; +} +div.CodeMirror span.CodeMirror-nonmatchingbracket { + color: #a22; +} +.CodeMirror-matchingtag { + background: rgba(255, 150, 0, 0.3); +} +.CodeMirror-activeline-background { + background: #e8f2ff; +} + +/* STOP */ + +/* The rest of this file contains styles related to the mechanics of + the editor. You probably shouldn't touch them. */ + +.CodeMirror { + position: relative; + overflow: hidden; + background: white; +} + +.CodeMirror-scroll { + overflow: scroll !important; /* Things will break if this is overridden */ + /* 30px is the magic margin used to hide the element's real scrollbars */ + /* See overflow: hidden in .CodeMirror */ + margin-bottom: -30px; + margin-right: -30px; + padding-bottom: 30px; + height: 100%; + outline: none; /* Prevent dragging from highlighting the element */ + position: relative; +} +.CodeMirror-sizer { + position: relative; + border-right: 30px solid transparent; +} + +/* The fake, visible scrollbars. Used to force redraw during scrolling + before actual scrolling happens, thus preventing shaking and + flickering artifacts. */ +.CodeMirror-vscrollbar, +.CodeMirror-hscrollbar, +.CodeMirror-scrollbar-filler, +.CodeMirror-gutter-filler { + position: absolute; + z-index: 6; + display: none; +} +.CodeMirror-vscrollbar { + right: 0; + top: 0; + overflow-x: hidden; + overflow-y: scroll; +} +.CodeMirror-hscrollbar { + bottom: 0; + left: 0; + overflow-y: hidden; + overflow-x: scroll; +} +.CodeMirror-scrollbar-filler { + right: 0; + bottom: 0; +} +.CodeMirror-gutter-filler { + left: 0; + bottom: 0; +} + +.CodeMirror-gutters { + position: absolute; + left: 0; + top: 0; + min-height: 100%; + z-index: 3; +} +.CodeMirror-gutter { + white-space: normal; + height: 100%; + display: inline-block; + vertical-align: top; + margin-bottom: -30px; +} +.CodeMirror-gutter-wrapper { + position: absolute; + z-index: 4; + background: none !important; + border: none !important; +} +.CodeMirror-gutter-background { + position: absolute; + top: 0; + bottom: 0; + z-index: 4; +} +.CodeMirror-gutter-elt { + position: absolute; + cursor: default; + z-index: 4; +} +.CodeMirror-gutter-wrapper ::selection { + background-color: transparent; +} +.CodeMirror-gutter-wrapper ::-moz-selection { + background-color: transparent; +} + +.CodeMirror-lines { + cursor: text; + min-height: 1px; /* prevents collapsing before first draw */ +} +.CodeMirror pre { + /* Reset some styles that the rest of the page might have set */ + -moz-border-radius: 0; + -webkit-border-radius: 0; + border-radius: 0; + border-width: 0; + background: transparent; + font-family: inherit; + font-size: inherit; + margin: 0; + white-space: pre; + word-wrap: normal; + line-height: inherit; + color: inherit; + z-index: 2; + position: relative; + overflow: visible; + -webkit-tap-highlight-color: transparent; + -webkit-font-variant-ligatures: contextual; + font-variant-ligatures: contextual; +} +.CodeMirror-wrap pre { + word-wrap: break-word; + white-space: pre-wrap; + word-break: normal; +} + +.CodeMirror-linebackground { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + z-index: 0; +} + +.CodeMirror-linewidget { + position: relative; + z-index: 2; + padding: 0.1px; /* Force widget margins to stay inside of the container */ +} + +.CodeMirror-widget { +} + +.CodeMirror-rtl pre { + direction: rtl; +} + +.CodeMirror-code { + outline: none; +} + +/* Force content-box sizing for the elements where we expect it */ +.CodeMirror-scroll, +.CodeMirror-sizer, +.CodeMirror-gutter, +.CodeMirror-gutters, +.CodeMirror-linenumber { + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +.CodeMirror-measure { + position: absolute; + width: 100%; + height: 0; + overflow: hidden; + visibility: hidden; +} + +.CodeMirror-cursor { + position: absolute; + pointer-events: none; +} +.CodeMirror-measure pre { + position: static; +} + +div.CodeMirror-cursors { + visibility: hidden; + position: relative; + z-index: 3; +} +div.CodeMirror-dragcursors { + visibility: visible; +} + +.CodeMirror-focused div.CodeMirror-cursors { + visibility: visible; +} + +.CodeMirror-selected { + background: #d9d9d9; +} +.CodeMirror-focused .CodeMirror-selected { + background: #d7d4f0; +} +.CodeMirror-crosshair { + cursor: crosshair; +} +.CodeMirror-line::selection, +.CodeMirror-line > span::selection, +.CodeMirror-line > span > span::selection { + background: #d7d4f0; +} +.CodeMirror-line::-moz-selection, +.CodeMirror-line > span::-moz-selection, +.CodeMirror-line > span > span::-moz-selection { + background: #d7d4f0; +} + +.cm-searching { + background-color: #ffa; + background-color: rgba(255, 255, 0, 0.4); +} + +/* Used to force a border model for a node */ +.cm-force-border { + padding-right: 0.1px; +} + +@media print { + /* Hide the cursor when printing */ + .CodeMirror div.CodeMirror-cursors { + visibility: hidden; + } +} + +/* See issue #2901 */ +.cm-tab-wrap-hack:after { + content: ""; +} + +/* Help users use markselection to safely style text background */ +span.CodeMirror-selectedtext { + background: none; +} + +.CodeMirror-hints { + position: absolute; + z-index: 10; + overflow: hidden; + list-style: none; + + margin: 0; + padding: 2px; + + -webkit-box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2); + box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2); + border-radius: 3px; + border: 1px solid silver; + + background: white; + font-size: 90%; + font-family: monospace; + + max-height: 20em; + overflow-y: auto; +} + +.CodeMirror-hint { + margin: 0; + padding: 0 4px; + border-radius: 2px; + white-space: pre; + color: black; + cursor: pointer; +} + +li.CodeMirror-hint-active { + background: #08f; + color: white; +} diff --git a/node_modules/scroll-cli/external/copyPaster.js b/node_modules/scroll-cli/external/copyPaster.js deleted file mode 100644 index 339eea3167..0000000000 --- a/node_modules/scroll-cli/external/copyPaster.js +++ /dev/null @@ -1,71 +0,0 @@ -class CopyPaster { - constructor(formId) { - this.formId = formId - } - formToTree() { - const formId = this.formId - const formData = new FormData(document.getElementById(formId)) - return Array.from(formData.entries()) - .filter(line => this._filterHiddenKeys(line[0])) - .map(line => `${this._formKeyToKeyword(line[0])} ${line[1].replace(/\n/g, "\n ")}`) - .join("\n") - } - _filterHiddenKeys(line) { - return line.startsWith("app[questions]") - } - _formKeyToKeyword(line) { - return line.replace("app[questions][", "").replace("]", "") - } - _keywordToFormKey(keyword) { - return `app[questions][${keyword}]` - } - formToGrammarWithStrongTyping() { - // todo - } - treeToForm(tree) { - new jtree.TreeNode(tree).forEach(node => { - const newValue = node.getContent() + (node.length ? "\n" + node.childrenToString() : "") - document.getElementsByName(this._keywordToFormKey(node.getWord(0)))[0].value = newValue - }) - } - _removeFromPage() { - const existing = document.getElementById("copyPaster") - if (existing) existing.remove() - } - reset() { - this._removeFromPage() - this._addToPage() - } - _addJtreeToPage() { - // https://jtree.treenotation.org/products/jtree.browser.js - } - startListening() { - let that = this - document.getElementById(this.formId).addEventListener("keyup", function () { - that._updateTextarea() - }) - } - _updateTextarea() { - document.getElementById("copyPasterTextArea").value = this.formToTree() - } - _addToPage() { - const div = document.createElement("div") - const textarea = document.createElement("textarea") - const header = document.createElement("h3") - header.innerHTML = "CopyPaster" - div.appendChild(header) - div.appendChild(textarea) - div.setAttribute("id", "copyPaster") - div.setAttribute("style", "position: fixed; bottom: 0; right: 0; width: 380px; height: 200px;") - textarea.setAttribute("style", "height: 100%;") - textarea.setAttribute("id", "copyPasterTextArea") - textarea.value = this.formToTree() - const that = this - textarea.addEventListener("change", function () { - that.treeToForm(textarea.value) - }) - document.body.appendChild(div) - } -} -a = new theCopyPaster("edit_app_243511") -a.reset() diff --git a/node_modules/scroll-cli/external/scrollLibs.js b/node_modules/scroll-cli/external/scrollLibs.js new file mode 100644 index 0000000000..18951d6de3 --- /dev/null +++ b/node_modules/scroll-cli/external/scrollLibs.js @@ -0,0 +1,19159 @@ +/* mousetrap v1.6.3 craig.is/killing/mice */ +(function(q,u,c){function v(a,b,g){a.addEventListener?a.addEventListener(b,g,!1):a.attachEvent("on"+b,g)}function z(a){if("keypress"==a.type){var b=String.fromCharCode(a.which);a.shiftKey||(b=b.toLowerCase());return b}return n[a.which]?n[a.which]:r[a.which]?r[a.which]:String.fromCharCode(a.which).toLowerCase()}function F(a){var b=[];a.shiftKey&&b.push("shift");a.altKey&&b.push("alt");a.ctrlKey&&b.push("ctrl");a.metaKey&&b.push("meta");return b}function w(a){return"shift"==a||"ctrl"==a||"alt"==a|| +"meta"==a}function A(a,b){var g,d=[];var e=a;"+"===e?e=["+"]:(e=e.replace(/\+{2}/g,"+plus"),e=e.split("+"));for(g=0;gc||n.hasOwnProperty(c)&&(p[n[c]]=c)}g=p[e]?"keydown":"keypress"}"keypress"==g&&d.length&&(g="keydown");return{key:m,modifiers:d,action:g}}function D(a,b){return null===a||a===u?!1:a===b?!0:D(a.parentNode,b)}function d(a){function b(a){a= +a||{};var b=!1,l;for(l in p)a[l]?b=!0:p[l]=0;b||(x=!1)}function g(a,b,t,f,g,d){var l,E=[],h=t.type;if(!k._callbacks[a])return[];"keyup"==h&&w(a)&&(b=[a]);for(l=0;l":".","?":"/","|":"\\"},B={option:"alt",command:"meta","return":"enter", +escape:"esc",plus:"+",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},p;for(c=1;20>c;++c)n[111+c]="f"+c;for(c=0;9>=c;++c)n[c+96]=c.toString();d.prototype.bind=function(a,b,c){a=a instanceof Array?a:[a];this._bindMultiple.call(this,a,b,c);return this};d.prototype.unbind=function(a,b){return this.bind.call(this,a,function(){},b)};d.prototype.trigger=function(a,b){if(this._directMap[a+":"+b])this._directMap[a+":"+b]({},a);return this};d.prototype.reset=function(){this._callbacks={}; +this._directMap={};return this};d.prototype.stopCallback=function(a,b){if(-1<(" "+b.className+" ").indexOf(" mousetrap ")||D(b,this.target))return!1;if("composedPath"in a&&"function"===typeof a.composedPath){var c=a.composedPath()[0];c!==a.target&&(b=c)}return"INPUT"==b.tagName||"SELECT"==b.tagName||"TEXTAREA"==b.tagName||b.isContentEditable};d.prototype.handleKey=function(){return this._handleKey.apply(this,arguments)};d.addKeycodes=function(a){for(var b in a)a.hasOwnProperty(b)&&(n[b]=a[b]);p=null}; +d.init=function(){var a=d(u),b;for(b in a)"_"!==b.charAt(0)&&(d[b]=function(b){return function(){return a[b].apply(a,arguments)}}(b))};d.init();q.Mousetrap=d;"undefined"!==typeof module&&module.exports&&(module.exports=d);"function"===typeof define&&define.amd&&define(function(){return d})}})("undefined"!==typeof window?window:null,"undefined"!==typeof window?document:null); + +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0",options:{classes:{},disabled:!1,create:null},_createWidget:function(t,e){e=b(e||this.defaultElement||this)[0],this.element=b(e),this.uuid=s++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=b(),this.hoverable=b(),this.focusable=b(),this.classesElementLookup={},e!==this&&(b.data(e,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===e&&this.destroy()}}),this.document=b(e.style?e.ownerDocument:e.document||e),this.window=b(this.document[0].defaultView||this.document[0].parentWindow)),this.options=b.widget.extend({},this.options,this._getCreateOptions(),t),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:b.noop,_create:b.noop,_init:b.noop,destroy:function(){var s=this;this._destroy(),b.each(this.classesElementLookup,function(t,e){s._removeClass(e,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:b.noop,widget:function(){return this.element},option:function(t,e){var s,i,o,n=t;if(0===arguments.length)return b.widget.extend({},this.options);if("string"==typeof t)if(n={},t=(s=t.split(".")).shift(),s.length){for(i=n[t]=b.widget.extend({},this.options[t]),o=0;o=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}}),b.ui.plugin={add:function(t,e,s){var i,o=b.ui[t].prototype;for(i in s)o.plugins[i]=o.plugins[i]||[],o.plugins[i].push([e,s[i]])},call:function(t,e,s,i){var o,n=t.plugins[e];if(n&&(i||t.element[0].parentNode&&11!==t.element[0].parentNode.nodeType))for(o=0;o").css("position","absolute").appendTo(t.parent()).outerWidth(t.outerWidth()).outerHeight(t.outerHeight()).offset(t.offset())[0]})},_unblockFrames:function(){this.iframeBlocks&&(this.iframeBlocks.remove(),delete this.iframeBlocks)},_blurActiveElement:function(t){var e=b.ui.safeActiveElement(this.document[0]);b(t.target).closest(e).length||b.ui.safeBlur(e)},_mouseStart:function(t){var e=this.options;return this.helper=this._createHelper(t),this._addClass(this.helper,"ui-draggable-dragging"),this._cacheHelperProportions(),b.ui.ddmanager&&(b.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(!0),this.offsetParent=this.helper.offsetParent(),this.hasFixedAncestor=0s[2]&&(n=s[2]+this.offset.click.left),t.pageY-this.offset.click.top>s[3]&&(r=s[3]+this.offset.click.top)),i.grid&&(t=i.grid[1]?this.originalPageY+Math.round((r-this.originalPageY)/i.grid[1])*i.grid[1]:this.originalPageY,r=!s||t-this.offset.click.top>=s[1]||t-this.offset.click.top>s[3]?t:t-this.offset.click.top>=s[1]?t-i.grid[1]:t+i.grid[1],t=i.grid[0]?this.originalPageX+Math.round((n-this.originalPageX)/i.grid[0])*i.grid[0]:this.originalPageX,n=!s||t-this.offset.click.left>=s[0]||t-this.offset.click.left>s[2]?t:t-this.offset.click.left>=s[0]?t-i.grid[0]:t+i.grid[0]),"y"===i.axis&&(n=this.originalPageX),"x"===i.axis&&(r=this.originalPageY)),{top:r-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.offset.scroll.top:o?0:this.offset.scroll.top),left:n-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.offset.scroll.left:o?0:this.offset.scroll.left)}},_clear:function(){this._removeClass(this.helper,"ui-draggable-dragging"),this.helper[0]===this.element[0]||this.cancelHelperRemoval||this.helper.remove(),this.helper=null,this.cancelHelperRemoval=!1,this.destroyOnClear&&this.destroy()},_trigger:function(t,e,s){return s=s||this._uiHash(),b.ui.plugin.call(this,t,[e,s,this],!0),/^(drag|start|stop)/.test(t)&&(this.positionAbs=this._convertPositionTo("absolute"),s.offset=this.positionAbs),b.Widget.prototype._trigger.call(this,t,e,s)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}}),b.ui.plugin.add("draggable","connectToSortable",{start:function(e,t,s){var i=b.extend({},t,{item:s.element});s.sortables=[],b(s.options.connectToSortable).each(function(){var t=b(this).sortable("instance");t&&!t.options.disabled&&(s.sortables.push(t),t.refreshPositions(),t._trigger("activate",e,i))})},stop:function(e,t,s){var i=b.extend({},t,{item:s.element});s.cancelHelperRemoval=!1,b.each(s.sortables,function(){var t=this;t.isOver?(t.isOver=0,s.cancelHelperRemoval=!0,t.cancelHelperRemoval=!1,t._storedCSS={position:t.placeholder.css("position"),top:t.placeholder.css("top"),left:t.placeholder.css("left")},t._mouseStop(e),t.options.helper=t.options._helper):(t.cancelHelperRemoval=!0,t._trigger("deactivate",e,i))})},drag:function(s,i,o){b.each(o.sortables,function(){var t=!1,e=this;e.positionAbs=o.positionAbs,e.helperProportions=o.helperProportions,e.offset.click=o.offset.click,e._intersectsWith(e.containerCache)&&(t=!0,b.each(o.sortables,function(){return this.positionAbs=o.positionAbs,this.helperProportions=o.helperProportions,this.offset.click=o.offset.click,t=this!==e&&this._intersectsWith(this.containerCache)&&b.contains(e.element[0],this.element[0])?!1:t})),t?(e.isOver||(e.isOver=1,o._parent=i.helper.parent(),e.currentItem=i.helper.appendTo(e.element).data("ui-sortable-item",!0),e.options._helper=e.options.helper,e.options.helper=function(){return i.helper[0]},s.target=e.currentItem[0],e._mouseCapture(s,!0),e._mouseStart(s,!0,!0),e.offset.click.top=o.offset.click.top,e.offset.click.left=o.offset.click.left,e.offset.parent.left-=o.offset.parent.left-e.offset.parent.left,e.offset.parent.top-=o.offset.parent.top-e.offset.parent.top,o._trigger("toSortable",s),o.dropped=e.element,b.each(o.sortables,function(){this.refreshPositions()}),o.currentItem=o.element,e.fromOutside=o),e.currentItem&&(e._mouseDrag(s),i.position=e.position)):e.isOver&&(e.isOver=0,e.cancelHelperRemoval=!0,e.options._revert=e.options.revert,e.options.revert=!1,e._trigger("out",s,e._uiHash(e)),e._mouseStop(s,!0),e.options.revert=e.options._revert,e.options.helper=e.options._helper,e.placeholder&&e.placeholder.remove(),i.helper.appendTo(o._parent),o._refreshOffsets(s),i.position=o._generatePosition(s,!0),o._trigger("fromSortable",s),o.dropped=!1,b.each(o.sortables,function(){this.refreshPositions()}))})}}),b.ui.plugin.add("draggable","cursor",{start:function(t,e,s){var i=b("body"),s=s.options;i.css("cursor")&&(s._cursor=i.css("cursor")),i.css("cursor",s.cursor)},stop:function(t,e,s){s=s.options;s._cursor&&b("body").css("cursor",s._cursor)}}),b.ui.plugin.add("draggable","opacity",{start:function(t,e,s){e=b(e.helper),s=s.options;e.css("opacity")&&(s._opacity=e.css("opacity")),e.css("opacity",s.opacity)},stop:function(t,e,s){s=s.options;s._opacity&&b(e.helper).css("opacity",s._opacity)}}),b.ui.plugin.add("draggable","scroll",{start:function(t,e,s){s.scrollParentNotHidden||(s.scrollParentNotHidden=s.helper.scrollParent(!1)),s.scrollParentNotHidden[0]!==s.document[0]&&"HTML"!==s.scrollParentNotHidden[0].tagName&&(s.overflowOffset=s.scrollParentNotHidden.offset())},drag:function(t,e,s){var i=s.options,o=!1,n=s.scrollParentNotHidden[0],r=s.document[0];n!==r&&"HTML"!==n.tagName?(i.axis&&"x"===i.axis||(s.overflowOffset.top+n.offsetHeight-t.pageY1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery); + +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +// This is CodeMirror (https://codemirror.net), a code editor +// implemented in JavaScript on top of the browser's DOM. +// +// You can find some technical background for some of the code below +// at http://marijnhaverbeke.nl/blog/#cm-internals . + +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.CodeMirror = factory()); +}(this, (function () { 'use strict'; + + // Kludges for bugs and behavior differences that can't be feature + // detected are enabled based on userAgent etc sniffing. + var userAgent = navigator.userAgent; + var platform = navigator.platform; + + var gecko = /gecko\/\d/i.test(userAgent); + var ie_upto10 = /MSIE \d/.test(userAgent); + var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(userAgent); + var edge = /Edge\/(\d+)/.exec(userAgent); + var ie = ie_upto10 || ie_11up || edge; + var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : +(edge || ie_11up)[1]); + var webkit = !edge && /WebKit\//.test(userAgent); + var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent); + var chrome = !edge && /Chrome\//.test(userAgent); + var presto = /Opera\//.test(userAgent); + var safari = /Apple Computer/.test(navigator.vendor); + var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent); + var phantom = /PhantomJS/.test(userAgent); + + var ios = !edge && /AppleWebKit/.test(userAgent) && /Mobile\/\w+/.test(userAgent); + var android = /Android/.test(userAgent); + // This is woefully incomplete. Suggestions for alternative methods welcome. + var mobile = ios || android || /webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(userAgent); + var mac = ios || /Mac/.test(platform); + var chromeOS = /\bCrOS\b/.test(userAgent); + var windows = /win/i.test(platform); + + var presto_version = presto && userAgent.match(/Version\/(\d*\.\d*)/); + if (presto_version) { presto_version = Number(presto_version[1]); } + if (presto_version && presto_version >= 15) { presto = false; webkit = true; } + // Some browsers use the wrong event properties to signal cmd/ctrl on OS X + var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11)); + var captureRightClick = gecko || (ie && ie_version >= 9); + + function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*") } + + var rmClass = function(node, cls) { + var current = node.className; + var match = classTest(cls).exec(current); + if (match) { + var after = current.slice(match.index + match[0].length); + node.className = current.slice(0, match.index) + (after ? match[1] + after : ""); + } + }; + + function removeChildren(e) { + for (var count = e.childNodes.length; count > 0; --count) + { e.removeChild(e.firstChild); } + return e + } + + function removeChildrenAndAdd(parent, e) { + return removeChildren(parent).appendChild(e) + } + + function elt(tag, content, className, style) { + var e = document.createElement(tag); + if (className) { e.className = className; } + if (style) { e.style.cssText = style; } + if (typeof content == "string") { e.appendChild(document.createTextNode(content)); } + else if (content) { for (var i = 0; i < content.length; ++i) { e.appendChild(content[i]); } } + return e + } + // wrapper for elt, which removes the elt from the accessibility tree + function eltP(tag, content, className, style) { + var e = elt(tag, content, className, style); + e.setAttribute("role", "presentation"); + return e + } + + var range; + if (document.createRange) { range = function(node, start, end, endNode) { + var r = document.createRange(); + r.setEnd(endNode || node, end); + r.setStart(node, start); + return r + }; } + else { range = function(node, start, end) { + var r = document.body.createTextRange(); + try { r.moveToElementText(node.parentNode); } + catch(e) { return r } + r.collapse(true); + r.moveEnd("character", end); + r.moveStart("character", start); + return r + }; } + + function contains(parent, child) { + if (child.nodeType == 3) // Android browser always returns false when child is a textnode + { child = child.parentNode; } + if (parent.contains) + { return parent.contains(child) } + do { + if (child.nodeType == 11) { child = child.host; } + if (child == parent) { return true } + } while (child = child.parentNode) + } + + function activeElt() { + // IE and Edge may throw an "Unspecified Error" when accessing document.activeElement. + // IE < 10 will throw when accessed while the page is loading or in an iframe. + // IE > 9 and Edge will throw when accessed in an iframe if document.body is unavailable. + var activeElement; + try { + activeElement = document.activeElement; + } catch(e) { + activeElement = document.body || null; + } + while (activeElement && activeElement.shadowRoot && activeElement.shadowRoot.activeElement) + { activeElement = activeElement.shadowRoot.activeElement; } + return activeElement + } + + function addClass(node, cls) { + var current = node.className; + if (!classTest(cls).test(current)) { node.className += (current ? " " : "") + cls; } + } + function joinClasses(a, b) { + var as = a.split(" "); + for (var i = 0; i < as.length; i++) + { if (as[i] && !classTest(as[i]).test(b)) { b += " " + as[i]; } } + return b + } + + var selectInput = function(node) { node.select(); }; + if (ios) // Mobile Safari apparently has a bug where select() is broken. + { selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length; }; } + else if (ie) // Suppress mysterious IE10 errors + { selectInput = function(node) { try { node.select(); } catch(_e) {} }; } + + function bind(f) { + var args = Array.prototype.slice.call(arguments, 1); + return function(){return f.apply(null, args)} + } + + function copyObj(obj, target, overwrite) { + if (!target) { target = {}; } + for (var prop in obj) + { if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop))) + { target[prop] = obj[prop]; } } + return target + } + + // Counts the column offset in a string, taking tabs into account. + // Used mostly to find indentation. + function countColumn(string, end, tabSize, startIndex, startValue) { + if (end == null) { + end = string.search(/[^\s\u00a0]/); + if (end == -1) { end = string.length; } + } + for (var i = startIndex || 0, n = startValue || 0;;) { + var nextTab = string.indexOf("\t", i); + if (nextTab < 0 || nextTab >= end) + { return n + (end - i) } + n += nextTab - i; + n += tabSize - (n % tabSize); + i = nextTab + 1; + } + } + + var Delayed = function() {this.id = null;}; + Delayed.prototype.set = function (ms, f) { + clearTimeout(this.id); + this.id = setTimeout(f, ms); + }; + + function indexOf(array, elt) { + for (var i = 0; i < array.length; ++i) + { if (array[i] == elt) { return i } } + return -1 + } + + // Number of pixels added to scroller and sizer to hide scrollbar + var scrollerGap = 30; + + // Returned or thrown by various protocols to signal 'I'm not + // handling this'. + var Pass = {toString: function(){return "CodeMirror.Pass"}}; + + // Reused option objects for setSelection & friends + var sel_dontScroll = {scroll: false}, sel_mouse = {origin: "*mouse"}, sel_move = {origin: "+move"}; + + // The inverse of countColumn -- find the offset that corresponds to + // a particular column. + function findColumn(string, goal, tabSize) { + for (var pos = 0, col = 0;;) { + var nextTab = string.indexOf("\t", pos); + if (nextTab == -1) { nextTab = string.length; } + var skipped = nextTab - pos; + if (nextTab == string.length || col + skipped >= goal) + { return pos + Math.min(skipped, goal - col) } + col += nextTab - pos; + col += tabSize - (col % tabSize); + pos = nextTab + 1; + if (col >= goal) { return pos } + } + } + + var spaceStrs = [""]; + function spaceStr(n) { + while (spaceStrs.length <= n) + { spaceStrs.push(lst(spaceStrs) + " "); } + return spaceStrs[n] + } + + function lst(arr) { return arr[arr.length-1] } + + function map(array, f) { + var out = []; + for (var i = 0; i < array.length; i++) { out[i] = f(array[i], i); } + return out + } + + function insertSorted(array, value, score) { + var pos = 0, priority = score(value); + while (pos < array.length && score(array[pos]) <= priority) { pos++; } + array.splice(pos, 0, value); + } + + function nothing() {} + + function createObj(base, props) { + var inst; + if (Object.create) { + inst = Object.create(base); + } else { + nothing.prototype = base; + inst = new nothing(); + } + if (props) { copyObj(props, inst); } + return inst + } + + var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; + function isWordCharBasic(ch) { + return /\w/.test(ch) || ch > "\x80" && + (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)) + } + function isWordChar(ch, helper) { + if (!helper) { return isWordCharBasic(ch) } + if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) { return true } + return helper.test(ch) + } + + function isEmpty(obj) { + for (var n in obj) { if (obj.hasOwnProperty(n) && obj[n]) { return false } } + return true + } + + // Extending unicode characters. A series of a non-extending char + + // any number of extending chars is treated as a single unit as far + // as editing and measuring is concerned. This is not fully correct, + // since some scripts/fonts/browsers also treat other configurations + // of code points as a group. + var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/; + function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch) } + + // Returns a number from the range [`0`; `str.length`] unless `pos` is outside that range. + function skipExtendingChars(str, pos, dir) { + while ((dir < 0 ? pos > 0 : pos < str.length) && isExtendingChar(str.charAt(pos))) { pos += dir; } + return pos + } + + // Returns the value from the range [`from`; `to`] that satisfies + // `pred` and is closest to `from`. Assumes that at least `to` + // satisfies `pred`. Supports `from` being greater than `to`. + function findFirst(pred, from, to) { + // At any point we are certain `to` satisfies `pred`, don't know + // whether `from` does. + var dir = from > to ? -1 : 1; + for (;;) { + if (from == to) { return from } + var midF = (from + to) / 2, mid = dir < 0 ? Math.ceil(midF) : Math.floor(midF); + if (mid == from) { return pred(mid) ? from : to } + if (pred(mid)) { to = mid; } + else { from = mid + dir; } + } + } + + // The display handles the DOM integration, both for input reading + // and content drawing. It holds references to DOM nodes and + // display-related state. + + function Display(place, doc, input) { + var d = this; + this.input = input; + + // Covers bottom-right square when both scrollbars are present. + d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler"); + d.scrollbarFiller.setAttribute("cm-not-content", "true"); + // Covers bottom of gutter when coverGutterNextToScrollbar is on + // and h scrollbar is present. + d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler"); + d.gutterFiller.setAttribute("cm-not-content", "true"); + // Will contain the actual code, positioned to cover the viewport. + d.lineDiv = eltP("div", null, "CodeMirror-code"); + // Elements are added to these to represent selection and cursors. + d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1"); + d.cursorDiv = elt("div", null, "CodeMirror-cursors"); + // A visibility: hidden element used to find the size of things. + d.measure = elt("div", null, "CodeMirror-measure"); + // When lines outside of the viewport are measured, they are drawn in this. + d.lineMeasure = elt("div", null, "CodeMirror-measure"); + // Wraps everything that needs to exist inside the vertically-padded coordinate system + d.lineSpace = eltP("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv], + null, "position: relative; outline: none"); + var lines = eltP("div", [d.lineSpace], "CodeMirror-lines"); + // Moved around its parent to cover visible view. + d.mover = elt("div", [lines], null, "position: relative"); + // Set to the height of the document, allowing scrolling. + d.sizer = elt("div", [d.mover], "CodeMirror-sizer"); + d.sizerWidth = null; + // Behavior of elts with overflow: auto and padding is + // inconsistent across browsers. This is used to ensure the + // scrollable area is big enough. + d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerGap + "px; width: 1px;"); + // Will contain the gutters, if any. + d.gutters = elt("div", null, "CodeMirror-gutters"); + d.lineGutter = null; + // Actual scrollable element. + d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll"); + d.scroller.setAttribute("tabIndex", "-1"); + // The element in which the editor lives. + d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror"); + + // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported) + if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; } + if (!webkit && !(gecko && mobile)) { d.scroller.draggable = true; } + + if (place) { + if (place.appendChild) { place.appendChild(d.wrapper); } + else { place(d.wrapper); } + } + + // Current rendered range (may be bigger than the view window). + d.viewFrom = d.viewTo = doc.first; + d.reportedViewFrom = d.reportedViewTo = doc.first; + // Information about the rendered lines. + d.view = []; + d.renderedView = null; + // Holds info about a single rendered line when it was rendered + // for measurement, while not in view. + d.externalMeasured = null; + // Empty space (in pixels) above the view + d.viewOffset = 0; + d.lastWrapHeight = d.lastWrapWidth = 0; + d.updateLineNumbers = null; + + d.nativeBarWidth = d.barHeight = d.barWidth = 0; + d.scrollbarsClipped = false; + + // Used to only resize the line number gutter when necessary (when + // the amount of lines crosses a boundary that makes its width change) + d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null; + // Set to true when a non-horizontal-scrolling line widget is + // added. As an optimization, line widget aligning is skipped when + // this is false. + d.alignWidgets = false; + + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; + + // Tracks the maximum line length so that the horizontal scrollbar + // can be kept static when scrolling. + d.maxLine = null; + d.maxLineLength = 0; + d.maxLineChanged = false; + + // Used for measuring wheel scrolling granularity + d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null; + + // True when shift is held down. + d.shift = false; + + // Used to track whether anything happened since the context menu + // was opened. + d.selForContextMenu = null; + + d.activeTouch = null; + + input.init(d); + } + + // Find the line object corresponding to the given line number. + function getLine(doc, n) { + n -= doc.first; + if (n < 0 || n >= doc.size) { throw new Error("There is no line " + (n + doc.first) + " in the document.") } + var chunk = doc; + while (!chunk.lines) { + for (var i = 0;; ++i) { + var child = chunk.children[i], sz = child.chunkSize(); + if (n < sz) { chunk = child; break } + n -= sz; + } + } + return chunk.lines[n] + } + + // Get the part of a document between two positions, as an array of + // strings. + function getBetween(doc, start, end) { + var out = [], n = start.line; + doc.iter(start.line, end.line + 1, function (line) { + var text = line.text; + if (n == end.line) { text = text.slice(0, end.ch); } + if (n == start.line) { text = text.slice(start.ch); } + out.push(text); + ++n; + }); + return out + } + // Get the lines between from and to, as array of strings. + function getLines(doc, from, to) { + var out = []; + doc.iter(from, to, function (line) { out.push(line.text); }); // iter aborts when callback returns truthy value + return out + } + + // Update the height of a line, propagating the height change + // upwards to parent nodes. + function updateLineHeight(line, height) { + var diff = height - line.height; + if (diff) { for (var n = line; n; n = n.parent) { n.height += diff; } } + } + + // Given a line object, find its line number by walking up through + // its parent links. + function lineNo(line) { + if (line.parent == null) { return null } + var cur = line.parent, no = indexOf(cur.lines, line); + for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { + for (var i = 0;; ++i) { + if (chunk.children[i] == cur) { break } + no += chunk.children[i].chunkSize(); + } + } + return no + cur.first + } + + // Find the line at the given vertical position, using the height + // information in the document tree. + function lineAtHeight(chunk, h) { + var n = chunk.first; + outer: do { + for (var i$1 = 0; i$1 < chunk.children.length; ++i$1) { + var child = chunk.children[i$1], ch = child.height; + if (h < ch) { chunk = child; continue outer } + h -= ch; + n += child.chunkSize(); + } + return n + } while (!chunk.lines) + var i = 0; + for (; i < chunk.lines.length; ++i) { + var line = chunk.lines[i], lh = line.height; + if (h < lh) { break } + h -= lh; + } + return n + i + } + + function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size} + + function lineNumberFor(options, i) { + return String(options.lineNumberFormatter(i + options.firstLineNumber)) + } + + // A Pos instance represents a position within the text. + function Pos(line, ch, sticky) { + if ( sticky === void 0 ) sticky = null; + + if (!(this instanceof Pos)) { return new Pos(line, ch, sticky) } + this.line = line; + this.ch = ch; + this.sticky = sticky; + } + + // Compare two positions, return 0 if they are the same, a negative + // number when a is less, and a positive number otherwise. + function cmp(a, b) { return a.line - b.line || a.ch - b.ch } + + function equalCursorPos(a, b) { return a.sticky == b.sticky && cmp(a, b) == 0 } + + function copyPos(x) {return Pos(x.line, x.ch)} + function maxPos(a, b) { return cmp(a, b) < 0 ? b : a } + function minPos(a, b) { return cmp(a, b) < 0 ? a : b } + + // Most of the external API clips given positions to make sure they + // actually exist within the document. + function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1))} + function clipPos(doc, pos) { + if (pos.line < doc.first) { return Pos(doc.first, 0) } + var last = doc.first + doc.size - 1; + if (pos.line > last) { return Pos(last, getLine(doc, last).text.length) } + return clipToLen(pos, getLine(doc, pos.line).text.length) + } + function clipToLen(pos, linelen) { + var ch = pos.ch; + if (ch == null || ch > linelen) { return Pos(pos.line, linelen) } + else if (ch < 0) { return Pos(pos.line, 0) } + else { return pos } + } + function clipPosArray(doc, array) { + var out = []; + for (var i = 0; i < array.length; i++) { out[i] = clipPos(doc, array[i]); } + return out + } + + // Optimize some code when these features are not used. + var sawReadOnlySpans = false, sawCollapsedSpans = false; + + function seeReadOnlySpans() { + sawReadOnlySpans = true; + } + + function seeCollapsedSpans() { + sawCollapsedSpans = true; + } + + // TEXTMARKER SPANS + + function MarkedSpan(marker, from, to) { + this.marker = marker; + this.from = from; this.to = to; + } + + // Search an array of spans for a span matching the given marker. + function getMarkedSpanFor(spans, marker) { + if (spans) { for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if (span.marker == marker) { return span } + } } + } + // Remove a span from an array, returning undefined if no spans are + // left (we don't store arrays for lines without spans). + function removeMarkedSpan(spans, span) { + var r; + for (var i = 0; i < spans.length; ++i) + { if (spans[i] != span) { (r || (r = [])).push(spans[i]); } } + return r + } + // Add a span to a line. + function addMarkedSpan(line, span) { + line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span]; + span.marker.attachLine(line); + } + + // Used for the algorithm that adjusts markers for a change in the + // document. These functions cut an array of spans at a given + // character position, returning an array of remaining chunks (or + // undefined if nothing remains). + function markedSpansBefore(old, startCh, isInsert) { + var nw; + if (old) { for (var i = 0; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); + if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) { + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh) + ;(nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to)); + } + } } + return nw + } + function markedSpansAfter(old, endCh, isInsert) { + var nw; + if (old) { for (var i = 0; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh); + if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) { + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh) + ;(nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh, + span.to == null ? null : span.to - endCh)); + } + } } + return nw + } + + // Given a change object, compute the new set of marker spans that + // cover the line in which the change took place. Removes spans + // entirely within the change, reconnects spans belonging to the + // same marker that appear on both sides of the change, and cuts off + // spans partially within the change. Returns an array of span + // arrays with one element for each line in (after) the change. + function stretchSpansOverChange(doc, change) { + if (change.full) { return null } + var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans; + var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans; + if (!oldFirst && !oldLast) { return null } + + var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0; + // Get the spans that 'stick out' on both sides + var first = markedSpansBefore(oldFirst, startCh, isInsert); + var last = markedSpansAfter(oldLast, endCh, isInsert); + + // Next, merge those two ends + var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0); + if (first) { + // Fix up .to properties of first + for (var i = 0; i < first.length; ++i) { + var span = first[i]; + if (span.to == null) { + var found = getMarkedSpanFor(last, span.marker); + if (!found) { span.to = startCh; } + else if (sameLine) { span.to = found.to == null ? null : found.to + offset; } + } + } + } + if (last) { + // Fix up .from in last (or move them into first in case of sameLine) + for (var i$1 = 0; i$1 < last.length; ++i$1) { + var span$1 = last[i$1]; + if (span$1.to != null) { span$1.to += offset; } + if (span$1.from == null) { + var found$1 = getMarkedSpanFor(first, span$1.marker); + if (!found$1) { + span$1.from = offset; + if (sameLine) { (first || (first = [])).push(span$1); } + } + } else { + span$1.from += offset; + if (sameLine) { (first || (first = [])).push(span$1); } + } + } + } + // Make sure we didn't create any zero-length spans + if (first) { first = clearEmptySpans(first); } + if (last && last != first) { last = clearEmptySpans(last); } + + var newMarkers = [first]; + if (!sameLine) { + // Fill gap with whole-line-spans + var gap = change.text.length - 2, gapMarkers; + if (gap > 0 && first) + { for (var i$2 = 0; i$2 < first.length; ++i$2) + { if (first[i$2].to == null) + { (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i$2].marker, null, null)); } } } + for (var i$3 = 0; i$3 < gap; ++i$3) + { newMarkers.push(gapMarkers); } + newMarkers.push(last); + } + return newMarkers + } + + // Remove spans that are empty and don't have a clearWhenEmpty + // option of false. + function clearEmptySpans(spans) { + for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false) + { spans.splice(i--, 1); } + } + if (!spans.length) { return null } + return spans + } + + // Used to 'clip' out readOnly ranges when making a change. + function removeReadOnlyRanges(doc, from, to) { + var markers = null; + doc.iter(from.line, to.line + 1, function (line) { + if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) { + var mark = line.markedSpans[i].marker; + if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) + { (markers || (markers = [])).push(mark); } + } } + }); + if (!markers) { return null } + var parts = [{from: from, to: to}]; + for (var i = 0; i < markers.length; ++i) { + var mk = markers[i], m = mk.find(0); + for (var j = 0; j < parts.length; ++j) { + var p = parts[j]; + if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) { continue } + var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to); + if (dfrom < 0 || !mk.inclusiveLeft && !dfrom) + { newParts.push({from: p.from, to: m.from}); } + if (dto > 0 || !mk.inclusiveRight && !dto) + { newParts.push({from: m.to, to: p.to}); } + parts.splice.apply(parts, newParts); + j += newParts.length - 3; + } + } + return parts + } + + // Connect or disconnect spans from a line. + function detachMarkedSpans(line) { + var spans = line.markedSpans; + if (!spans) { return } + for (var i = 0; i < spans.length; ++i) + { spans[i].marker.detachLine(line); } + line.markedSpans = null; + } + function attachMarkedSpans(line, spans) { + if (!spans) { return } + for (var i = 0; i < spans.length; ++i) + { spans[i].marker.attachLine(line); } + line.markedSpans = spans; + } + + // Helpers used when computing which overlapping collapsed span + // counts as the larger one. + function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0 } + function extraRight(marker) { return marker.inclusiveRight ? 1 : 0 } + + // Returns a number indicating which of two overlapping collapsed + // spans is larger (and thus includes the other). Falls back to + // comparing ids when the spans cover exactly the same range. + function compareCollapsedMarkers(a, b) { + var lenDiff = a.lines.length - b.lines.length; + if (lenDiff != 0) { return lenDiff } + var aPos = a.find(), bPos = b.find(); + var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b); + if (fromCmp) { return -fromCmp } + var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b); + if (toCmp) { return toCmp } + return b.id - a.id + } + + // Find out whether a line ends or starts in a collapsed span. If + // so, return the marker for that span. + function collapsedSpanAtSide(line, start) { + var sps = sawCollapsedSpans && line.markedSpans, found; + if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) { + sp = sps[i]; + if (sp.marker.collapsed && (start ? sp.from : sp.to) == null && + (!found || compareCollapsedMarkers(found, sp.marker) < 0)) + { found = sp.marker; } + } } + return found + } + function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true) } + function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false) } + + function collapsedSpanAround(line, ch) { + var sps = sawCollapsedSpans && line.markedSpans, found; + if (sps) { for (var i = 0; i < sps.length; ++i) { + var sp = sps[i]; + if (sp.marker.collapsed && (sp.from == null || sp.from < ch) && (sp.to == null || sp.to > ch) && + (!found || compareCollapsedMarkers(found, sp.marker) < 0)) { found = sp.marker; } + } } + return found + } + + // Test whether there exists a collapsed span that partially + // overlaps (covers the start or end, but not both) of a new span. + // Such overlap is not allowed. + function conflictingCollapsedRange(doc, lineNo$$1, from, to, marker) { + var line = getLine(doc, lineNo$$1); + var sps = sawCollapsedSpans && line.markedSpans; + if (sps) { for (var i = 0; i < sps.length; ++i) { + var sp = sps[i]; + if (!sp.marker.collapsed) { continue } + var found = sp.marker.find(0); + var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker); + var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker); + if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) { continue } + if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.to, from) >= 0 : cmp(found.to, from) > 0) || + fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.from, to) <= 0 : cmp(found.from, to) < 0)) + { return true } + } } + } + + // A visual line is a line as drawn on the screen. Folding, for + // example, can cause multiple logical lines to appear on the same + // visual line. This finds the start of the visual line that the + // given line is part of (usually that is the line itself). + function visualLine(line) { + var merged; + while (merged = collapsedSpanAtStart(line)) + { line = merged.find(-1, true).line; } + return line + } + + function visualLineEnd(line) { + var merged; + while (merged = collapsedSpanAtEnd(line)) + { line = merged.find(1, true).line; } + return line + } + + // Returns an array of logical lines that continue the visual line + // started by the argument, or undefined if there are no such lines. + function visualLineContinued(line) { + var merged, lines; + while (merged = collapsedSpanAtEnd(line)) { + line = merged.find(1, true).line + ;(lines || (lines = [])).push(line); + } + return lines + } + + // Get the line number of the start of the visual line that the + // given line number is part of. + function visualLineNo(doc, lineN) { + var line = getLine(doc, lineN), vis = visualLine(line); + if (line == vis) { return lineN } + return lineNo(vis) + } + + // Get the line number of the start of the next visual line after + // the given line. + function visualLineEndNo(doc, lineN) { + if (lineN > doc.lastLine()) { return lineN } + var line = getLine(doc, lineN), merged; + if (!lineIsHidden(doc, line)) { return lineN } + while (merged = collapsedSpanAtEnd(line)) + { line = merged.find(1, true).line; } + return lineNo(line) + 1 + } + + // Compute whether a line is hidden. Lines count as hidden when they + // are part of a visual line that starts with another line, or when + // they are entirely covered by collapsed, non-widget span. + function lineIsHidden(doc, line) { + var sps = sawCollapsedSpans && line.markedSpans; + if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) { + sp = sps[i]; + if (!sp.marker.collapsed) { continue } + if (sp.from == null) { return true } + if (sp.marker.widgetNode) { continue } + if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) + { return true } + } } + } + function lineIsHiddenInner(doc, line, span) { + if (span.to == null) { + var end = span.marker.find(1, true); + return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker)) + } + if (span.marker.inclusiveRight && span.to == line.text.length) + { return true } + for (var sp = (void 0), i = 0; i < line.markedSpans.length; ++i) { + sp = line.markedSpans[i]; + if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to && + (sp.to == null || sp.to != span.from) && + (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && + lineIsHiddenInner(doc, line, sp)) { return true } + } + } + + // Find the height above the given line. + function heightAtLine(lineObj) { + lineObj = visualLine(lineObj); + + var h = 0, chunk = lineObj.parent; + for (var i = 0; i < chunk.lines.length; ++i) { + var line = chunk.lines[i]; + if (line == lineObj) { break } + else { h += line.height; } + } + for (var p = chunk.parent; p; chunk = p, p = chunk.parent) { + for (var i$1 = 0; i$1 < p.children.length; ++i$1) { + var cur = p.children[i$1]; + if (cur == chunk) { break } + else { h += cur.height; } + } + } + return h + } + + // Compute the character length of a line, taking into account + // collapsed ranges (see markText) that might hide parts, and join + // other lines onto it. + function lineLength(line) { + if (line.height == 0) { return 0 } + var len = line.text.length, merged, cur = line; + while (merged = collapsedSpanAtStart(cur)) { + var found = merged.find(0, true); + cur = found.from.line; + len += found.from.ch - found.to.ch; + } + cur = line; + while (merged = collapsedSpanAtEnd(cur)) { + var found$1 = merged.find(0, true); + len -= cur.text.length - found$1.from.ch; + cur = found$1.to.line; + len += cur.text.length - found$1.to.ch; + } + return len + } + + // Find the longest line in the document. + function findMaxLine(cm) { + var d = cm.display, doc = cm.doc; + d.maxLine = getLine(doc, doc.first); + d.maxLineLength = lineLength(d.maxLine); + d.maxLineChanged = true; + doc.iter(function (line) { + var len = lineLength(line); + if (len > d.maxLineLength) { + d.maxLineLength = len; + d.maxLine = line; + } + }); + } + + // BIDI HELPERS + + function iterateBidiSections(order, from, to, f) { + if (!order) { return f(from, to, "ltr", 0) } + var found = false; + for (var i = 0; i < order.length; ++i) { + var part = order[i]; + if (part.from < to && part.to > from || from == to && part.to == from) { + f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr", i); + found = true; + } + } + if (!found) { f(from, to, "ltr"); } + } + + var bidiOther = null; + function getBidiPartAt(order, ch, sticky) { + var found; + bidiOther = null; + for (var i = 0; i < order.length; ++i) { + var cur = order[i]; + if (cur.from < ch && cur.to > ch) { return i } + if (cur.to == ch) { + if (cur.from != cur.to && sticky == "before") { found = i; } + else { bidiOther = i; } + } + if (cur.from == ch) { + if (cur.from != cur.to && sticky != "before") { found = i; } + else { bidiOther = i; } + } + } + return found != null ? found : bidiOther + } + + // Bidirectional ordering algorithm + // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm + // that this (partially) implements. + + // One-char codes used for character types: + // L (L): Left-to-Right + // R (R): Right-to-Left + // r (AL): Right-to-Left Arabic + // 1 (EN): European Number + // + (ES): European Number Separator + // % (ET): European Number Terminator + // n (AN): Arabic Number + // , (CS): Common Number Separator + // m (NSM): Non-Spacing Mark + // b (BN): Boundary Neutral + // s (B): Paragraph Separator + // t (S): Segment Separator + // w (WS): Whitespace + // N (ON): Other Neutrals + + // Returns null if characters are ordered as they appear + // (left-to-right), or an array of sections ({from, to, level} + // objects) in the order in which they occur visually. + var bidiOrdering = (function() { + // Character types for codepoints 0 to 0xff + var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN"; + // Character types for codepoints 0x600 to 0x6f9 + var arabicTypes = "nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmnNmmmmmmrrmmNmmmmrr1111111111"; + function charType(code) { + if (code <= 0xf7) { return lowTypes.charAt(code) } + else if (0x590 <= code && code <= 0x5f4) { return "R" } + else if (0x600 <= code && code <= 0x6f9) { return arabicTypes.charAt(code - 0x600) } + else if (0x6ee <= code && code <= 0x8ac) { return "r" } + else if (0x2000 <= code && code <= 0x200b) { return "w" } + else if (code == 0x200c) { return "b" } + else { return "L" } + } + + var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/; + var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/; + + function BidiSpan(level, from, to) { + this.level = level; + this.from = from; this.to = to; + } + + return function(str, direction) { + var outerType = direction == "ltr" ? "L" : "R"; + + if (str.length == 0 || direction == "ltr" && !bidiRE.test(str)) { return false } + var len = str.length, types = []; + for (var i = 0; i < len; ++i) + { types.push(charType(str.charCodeAt(i))); } + + // W1. Examine each non-spacing mark (NSM) in the level run, and + // change the type of the NSM to the type of the previous + // character. If the NSM is at the start of the level run, it will + // get the type of sor. + for (var i$1 = 0, prev = outerType; i$1 < len; ++i$1) { + var type = types[i$1]; + if (type == "m") { types[i$1] = prev; } + else { prev = type; } + } + + // W2. Search backwards from each instance of a European number + // until the first strong type (R, L, AL, or sor) is found. If an + // AL is found, change the type of the European number to Arabic + // number. + // W3. Change all ALs to R. + for (var i$2 = 0, cur = outerType; i$2 < len; ++i$2) { + var type$1 = types[i$2]; + if (type$1 == "1" && cur == "r") { types[i$2] = "n"; } + else if (isStrong.test(type$1)) { cur = type$1; if (type$1 == "r") { types[i$2] = "R"; } } + } + + // W4. A single European separator between two European numbers + // changes to a European number. A single common separator between + // two numbers of the same type changes to that type. + for (var i$3 = 1, prev$1 = types[0]; i$3 < len - 1; ++i$3) { + var type$2 = types[i$3]; + if (type$2 == "+" && prev$1 == "1" && types[i$3+1] == "1") { types[i$3] = "1"; } + else if (type$2 == "," && prev$1 == types[i$3+1] && + (prev$1 == "1" || prev$1 == "n")) { types[i$3] = prev$1; } + prev$1 = type$2; + } + + // W5. A sequence of European terminators adjacent to European + // numbers changes to all European numbers. + // W6. Otherwise, separators and terminators change to Other + // Neutral. + for (var i$4 = 0; i$4 < len; ++i$4) { + var type$3 = types[i$4]; + if (type$3 == ",") { types[i$4] = "N"; } + else if (type$3 == "%") { + var end = (void 0); + for (end = i$4 + 1; end < len && types[end] == "%"; ++end) {} + var replace = (i$4 && types[i$4-1] == "!") || (end < len && types[end] == "1") ? "1" : "N"; + for (var j = i$4; j < end; ++j) { types[j] = replace; } + i$4 = end - 1; + } + } + + // W7. Search backwards from each instance of a European number + // until the first strong type (R, L, or sor) is found. If an L is + // found, then change the type of the European number to L. + for (var i$5 = 0, cur$1 = outerType; i$5 < len; ++i$5) { + var type$4 = types[i$5]; + if (cur$1 == "L" && type$4 == "1") { types[i$5] = "L"; } + else if (isStrong.test(type$4)) { cur$1 = type$4; } + } + + // N1. A sequence of neutrals takes the direction of the + // surrounding strong text if the text on both sides has the same + // direction. European and Arabic numbers act as if they were R in + // terms of their influence on neutrals. Start-of-level-run (sor) + // and end-of-level-run (eor) are used at level run boundaries. + // N2. Any remaining neutrals take the embedding direction. + for (var i$6 = 0; i$6 < len; ++i$6) { + if (isNeutral.test(types[i$6])) { + var end$1 = (void 0); + for (end$1 = i$6 + 1; end$1 < len && isNeutral.test(types[end$1]); ++end$1) {} + var before = (i$6 ? types[i$6-1] : outerType) == "L"; + var after = (end$1 < len ? types[end$1] : outerType) == "L"; + var replace$1 = before == after ? (before ? "L" : "R") : outerType; + for (var j$1 = i$6; j$1 < end$1; ++j$1) { types[j$1] = replace$1; } + i$6 = end$1 - 1; + } + } + + // Here we depart from the documented algorithm, in order to avoid + // building up an actual levels array. Since there are only three + // levels (0, 1, 2) in an implementation that doesn't take + // explicit embedding into account, we can build up the order on + // the fly, without following the level-based algorithm. + var order = [], m; + for (var i$7 = 0; i$7 < len;) { + if (countsAsLeft.test(types[i$7])) { + var start = i$7; + for (++i$7; i$7 < len && countsAsLeft.test(types[i$7]); ++i$7) {} + order.push(new BidiSpan(0, start, i$7)); + } else { + var pos = i$7, at = order.length; + for (++i$7; i$7 < len && types[i$7] != "L"; ++i$7) {} + for (var j$2 = pos; j$2 < i$7;) { + if (countsAsNum.test(types[j$2])) { + if (pos < j$2) { order.splice(at, 0, new BidiSpan(1, pos, j$2)); } + var nstart = j$2; + for (++j$2; j$2 < i$7 && countsAsNum.test(types[j$2]); ++j$2) {} + order.splice(at, 0, new BidiSpan(2, nstart, j$2)); + pos = j$2; + } else { ++j$2; } + } + if (pos < i$7) { order.splice(at, 0, new BidiSpan(1, pos, i$7)); } + } + } + if (direction == "ltr") { + if (order[0].level == 1 && (m = str.match(/^\s+/))) { + order[0].from = m[0].length; + order.unshift(new BidiSpan(0, 0, m[0].length)); + } + if (lst(order).level == 1 && (m = str.match(/\s+$/))) { + lst(order).to -= m[0].length; + order.push(new BidiSpan(0, len - m[0].length, len)); + } + } + + return direction == "rtl" ? order.reverse() : order + } + })(); + + // Get the bidi ordering for the given line (and cache it). Returns + // false for lines that are fully left-to-right, and an array of + // BidiSpan objects otherwise. + function getOrder(line, direction) { + var order = line.order; + if (order == null) { order = line.order = bidiOrdering(line.text, direction); } + return order + } + + // EVENT HANDLING + + // Lightweight event framework. on/off also work on DOM nodes, + // registering native DOM handlers. + + var noHandlers = []; + + var on = function(emitter, type, f) { + if (emitter.addEventListener) { + emitter.addEventListener(type, f, false); + } else if (emitter.attachEvent) { + emitter.attachEvent("on" + type, f); + } else { + var map$$1 = emitter._handlers || (emitter._handlers = {}); + map$$1[type] = (map$$1[type] || noHandlers).concat(f); + } + }; + + function getHandlers(emitter, type) { + return emitter._handlers && emitter._handlers[type] || noHandlers + } + + function off(emitter, type, f) { + if (emitter.removeEventListener) { + emitter.removeEventListener(type, f, false); + } else if (emitter.detachEvent) { + emitter.detachEvent("on" + type, f); + } else { + var map$$1 = emitter._handlers, arr = map$$1 && map$$1[type]; + if (arr) { + var index = indexOf(arr, f); + if (index > -1) + { map$$1[type] = arr.slice(0, index).concat(arr.slice(index + 1)); } + } + } + } + + function signal(emitter, type /*, values...*/) { + var handlers = getHandlers(emitter, type); + if (!handlers.length) { return } + var args = Array.prototype.slice.call(arguments, 2); + for (var i = 0; i < handlers.length; ++i) { handlers[i].apply(null, args); } + } + + // The DOM events that CodeMirror handles can be overridden by + // registering a (non-DOM) handler on the editor for the event name, + // and preventDefault-ing the event in that handler. + function signalDOMEvent(cm, e, override) { + if (typeof e == "string") + { e = {type: e, preventDefault: function() { this.defaultPrevented = true; }}; } + signal(cm, override || e.type, cm, e); + return e_defaultPrevented(e) || e.codemirrorIgnore + } + + function signalCursorActivity(cm) { + var arr = cm._handlers && cm._handlers.cursorActivity; + if (!arr) { return } + var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []); + for (var i = 0; i < arr.length; ++i) { if (indexOf(set, arr[i]) == -1) + { set.push(arr[i]); } } + } + + function hasHandler(emitter, type) { + return getHandlers(emitter, type).length > 0 + } + + // Add on and off methods to a constructor's prototype, to make + // registering events on such objects more convenient. + function eventMixin(ctor) { + ctor.prototype.on = function(type, f) {on(this, type, f);}; + ctor.prototype.off = function(type, f) {off(this, type, f);}; + } + + // Due to the fact that we still support jurassic IE versions, some + // compatibility wrappers are needed. + + function e_preventDefault(e) { + if (e.preventDefault) { e.preventDefault(); } + else { e.returnValue = false; } + } + function e_stopPropagation(e) { + if (e.stopPropagation) { e.stopPropagation(); } + else { e.cancelBubble = true; } + } + function e_defaultPrevented(e) { + return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false + } + function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);} + + function e_target(e) {return e.target || e.srcElement} + function e_button(e) { + var b = e.which; + if (b == null) { + if (e.button & 1) { b = 1; } + else if (e.button & 2) { b = 3; } + else if (e.button & 4) { b = 2; } + } + if (mac && e.ctrlKey && b == 1) { b = 3; } + return b + } + + // Detect drag-and-drop + var dragAndDrop = function() { + // There is *some* kind of drag-and-drop support in IE6-8, but I + // couldn't get it to work yet. + if (ie && ie_version < 9) { return false } + var div = elt('div'); + return "draggable" in div || "dragDrop" in div + }(); + + var zwspSupported; + function zeroWidthElement(measure) { + if (zwspSupported == null) { + var test = elt("span", "\u200b"); + removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")])); + if (measure.firstChild.offsetHeight != 0) + { zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8); } + } + var node = zwspSupported ? elt("span", "\u200b") : + elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px"); + node.setAttribute("cm-text", ""); + return node + } + + // Feature-detect IE's crummy client rect reporting for bidi text + var badBidiRects; + function hasBadBidiRects(measure) { + if (badBidiRects != null) { return badBidiRects } + var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA")); + var r0 = range(txt, 0, 1).getBoundingClientRect(); + var r1 = range(txt, 1, 2).getBoundingClientRect(); + removeChildren(measure); + if (!r0 || r0.left == r0.right) { return false } // Safari returns null in some cases (#2780) + return badBidiRects = (r1.right - r0.right < 3) + } + + // See if "".split is the broken IE version, if so, provide an + // alternative way to split lines. + var splitLinesAuto = "\n\nb".split(/\n/).length != 3 ? function (string) { + var pos = 0, result = [], l = string.length; + while (pos <= l) { + var nl = string.indexOf("\n", pos); + if (nl == -1) { nl = string.length; } + var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); + var rt = line.indexOf("\r"); + if (rt != -1) { + result.push(line.slice(0, rt)); + pos += rt + 1; + } else { + result.push(line); + pos = nl + 1; + } + } + return result + } : function (string) { return string.split(/\r\n?|\n/); }; + + var hasSelection = window.getSelection ? function (te) { + try { return te.selectionStart != te.selectionEnd } + catch(e) { return false } + } : function (te) { + var range$$1; + try {range$$1 = te.ownerDocument.selection.createRange();} + catch(e) {} + if (!range$$1 || range$$1.parentElement() != te) { return false } + return range$$1.compareEndPoints("StartToEnd", range$$1) != 0 + }; + + var hasCopyEvent = (function () { + var e = elt("div"); + if ("oncopy" in e) { return true } + e.setAttribute("oncopy", "return;"); + return typeof e.oncopy == "function" + })(); + + var badZoomedRects = null; + function hasBadZoomedRects(measure) { + if (badZoomedRects != null) { return badZoomedRects } + var node = removeChildrenAndAdd(measure, elt("span", "x")); + var normal = node.getBoundingClientRect(); + var fromRange = range(node, 0, 1).getBoundingClientRect(); + return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1 + } + + // Known modes, by name and by MIME + var modes = {}, mimeModes = {}; + + // Extra arguments are stored as the mode's dependencies, which is + // used by (legacy) mechanisms like loadmode.js to automatically + // load a mode. (Preferred mechanism is the require/define calls.) + function defineMode(name, mode) { + if (arguments.length > 2) + { mode.dependencies = Array.prototype.slice.call(arguments, 2); } + modes[name] = mode; + } + + function defineMIME(mime, spec) { + mimeModes[mime] = spec; + } + + // Given a MIME type, a {name, ...options} config object, or a name + // string, return a mode config object. + function resolveMode(spec) { + if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { + spec = mimeModes[spec]; + } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { + var found = mimeModes[spec.name]; + if (typeof found == "string") { found = {name: found}; } + spec = createObj(found, spec); + spec.name = found.name; + } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { + return resolveMode("application/xml") + } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+json$/.test(spec)) { + return resolveMode("application/json") + } + if (typeof spec == "string") { return {name: spec} } + else { return spec || {name: "null"} } + } + + // Given a mode spec (anything that resolveMode accepts), find and + // initialize an actual mode object. + function getMode(options, spec) { + spec = resolveMode(spec); + var mfactory = modes[spec.name]; + if (!mfactory) { return getMode(options, "text/plain") } + var modeObj = mfactory(options, spec); + if (modeExtensions.hasOwnProperty(spec.name)) { + var exts = modeExtensions[spec.name]; + for (var prop in exts) { + if (!exts.hasOwnProperty(prop)) { continue } + if (modeObj.hasOwnProperty(prop)) { modeObj["_" + prop] = modeObj[prop]; } + modeObj[prop] = exts[prop]; + } + } + modeObj.name = spec.name; + if (spec.helperType) { modeObj.helperType = spec.helperType; } + if (spec.modeProps) { for (var prop$1 in spec.modeProps) + { modeObj[prop$1] = spec.modeProps[prop$1]; } } + + return modeObj + } + + // This can be used to attach properties to mode objects from + // outside the actual mode definition. + var modeExtensions = {}; + function extendMode(mode, properties) { + var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}); + copyObj(properties, exts); + } + + function copyState(mode, state) { + if (state === true) { return state } + if (mode.copyState) { return mode.copyState(state) } + var nstate = {}; + for (var n in state) { + var val = state[n]; + if (val instanceof Array) { val = val.concat([]); } + nstate[n] = val; + } + return nstate + } + + // Given a mode and a state (for that mode), find the inner mode and + // state at the position that the state refers to. + function innerMode(mode, state) { + var info; + while (mode.innerMode) { + info = mode.innerMode(state); + if (!info || info.mode == mode) { break } + state = info.state; + mode = info.mode; + } + return info || {mode: mode, state: state} + } + + function startState(mode, a1, a2) { + return mode.startState ? mode.startState(a1, a2) : true + } + + // STRING STREAM + + // Fed to the mode parsers, provides helper functions to make + // parsers more succinct. + + var StringStream = function(string, tabSize, lineOracle) { + this.pos = this.start = 0; + this.string = string; + this.tabSize = tabSize || 8; + this.lastColumnPos = this.lastColumnValue = 0; + this.lineStart = 0; + this.lineOracle = lineOracle; + }; + + StringStream.prototype.eol = function () {return this.pos >= this.string.length}; + StringStream.prototype.sol = function () {return this.pos == this.lineStart}; + StringStream.prototype.peek = function () {return this.string.charAt(this.pos) || undefined}; + StringStream.prototype.next = function () { + if (this.pos < this.string.length) + { return this.string.charAt(this.pos++) } + }; + StringStream.prototype.eat = function (match) { + var ch = this.string.charAt(this.pos); + var ok; + if (typeof match == "string") { ok = ch == match; } + else { ok = ch && (match.test ? match.test(ch) : match(ch)); } + if (ok) {++this.pos; return ch} + }; + StringStream.prototype.eatWhile = function (match) { + var start = this.pos; + while (this.eat(match)){} + return this.pos > start + }; + StringStream.prototype.eatSpace = function () { + var this$1 = this; + + var start = this.pos; + while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) { ++this$1.pos; } + return this.pos > start + }; + StringStream.prototype.skipToEnd = function () {this.pos = this.string.length;}; + StringStream.prototype.skipTo = function (ch) { + var found = this.string.indexOf(ch, this.pos); + if (found > -1) {this.pos = found; return true} + }; + StringStream.prototype.backUp = function (n) {this.pos -= n;}; + StringStream.prototype.column = function () { + if (this.lastColumnPos < this.start) { + this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue); + this.lastColumnPos = this.start; + } + return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0) + }; + StringStream.prototype.indentation = function () { + return countColumn(this.string, null, this.tabSize) - + (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0) + }; + StringStream.prototype.match = function (pattern, consume, caseInsensitive) { + if (typeof pattern == "string") { + var cased = function (str) { return caseInsensitive ? str.toLowerCase() : str; }; + var substr = this.string.substr(this.pos, pattern.length); + if (cased(substr) == cased(pattern)) { + if (consume !== false) { this.pos += pattern.length; } + return true + } + } else { + var match = this.string.slice(this.pos).match(pattern); + if (match && match.index > 0) { return null } + if (match && consume !== false) { this.pos += match[0].length; } + return match + } + }; + StringStream.prototype.current = function (){return this.string.slice(this.start, this.pos)}; + StringStream.prototype.hideFirstChars = function (n, inner) { + this.lineStart += n; + try { return inner() } + finally { this.lineStart -= n; } + }; + StringStream.prototype.lookAhead = function (n) { + var oracle = this.lineOracle; + return oracle && oracle.lookAhead(n) + }; + StringStream.prototype.baseToken = function () { + var oracle = this.lineOracle; + return oracle && oracle.baseToken(this.pos) + }; + + var SavedContext = function(state, lookAhead) { + this.state = state; + this.lookAhead = lookAhead; + }; + + var Context = function(doc, state, line, lookAhead) { + this.state = state; + this.doc = doc; + this.line = line; + this.maxLookAhead = lookAhead || 0; + this.baseTokens = null; + this.baseTokenPos = 1; + }; + + Context.prototype.lookAhead = function (n) { + var line = this.doc.getLine(this.line + n); + if (line != null && n > this.maxLookAhead) { this.maxLookAhead = n; } + return line + }; + + Context.prototype.baseToken = function (n) { + var this$1 = this; + + if (!this.baseTokens) { return null } + while (this.baseTokens[this.baseTokenPos] <= n) + { this$1.baseTokenPos += 2; } + var type = this.baseTokens[this.baseTokenPos + 1]; + return {type: type && type.replace(/( |^)overlay .*/, ""), + size: this.baseTokens[this.baseTokenPos] - n} + }; + + Context.prototype.nextLine = function () { + this.line++; + if (this.maxLookAhead > 0) { this.maxLookAhead--; } + }; + + Context.fromSaved = function (doc, saved, line) { + if (saved instanceof SavedContext) + { return new Context(doc, copyState(doc.mode, saved.state), line, saved.lookAhead) } + else + { return new Context(doc, copyState(doc.mode, saved), line) } + }; + + Context.prototype.save = function (copy) { + var state = copy !== false ? copyState(this.doc.mode, this.state) : this.state; + return this.maxLookAhead > 0 ? new SavedContext(state, this.maxLookAhead) : state + }; + + + // Compute a style array (an array starting with a mode generation + // -- for invalidation -- followed by pairs of end positions and + // style strings), which is used to highlight the tokens on the + // line. + function highlightLine(cm, line, context, forceToEnd) { + // A styles array always starts with a number identifying the + // mode/overlays that it is based on (for easy invalidation). + var st = [cm.state.modeGen], lineClasses = {}; + // Compute the base array of styles + runMode(cm, line.text, cm.doc.mode, context, function (end, style) { return st.push(end, style); }, + lineClasses, forceToEnd); + var state = context.state; + + // Run overlays, adjust style array. + var loop = function ( o ) { + context.baseTokens = st; + var overlay = cm.state.overlays[o], i = 1, at = 0; + context.state = true; + runMode(cm, line.text, overlay.mode, context, function (end, style) { + var start = i; + // Ensure there's a token end at the current position, and that i points at it + while (at < end) { + var i_end = st[i]; + if (i_end > end) + { st.splice(i, 1, end, st[i+1], i_end); } + i += 2; + at = Math.min(end, i_end); + } + if (!style) { return } + if (overlay.opaque) { + st.splice(start, i - start, end, "overlay " + style); + i = start + 2; + } else { + for (; start < i; start += 2) { + var cur = st[start+1]; + st[start+1] = (cur ? cur + " " : "") + "overlay " + style; + } + } + }, lineClasses); + context.state = state; + context.baseTokens = null; + context.baseTokenPos = 1; + }; + + for (var o = 0; o < cm.state.overlays.length; ++o) loop( o ); + + return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null} + } + + function getLineStyles(cm, line, updateFrontier) { + if (!line.styles || line.styles[0] != cm.state.modeGen) { + var context = getContextBefore(cm, lineNo(line)); + var resetState = line.text.length > cm.options.maxHighlightLength && copyState(cm.doc.mode, context.state); + var result = highlightLine(cm, line, context); + if (resetState) { context.state = resetState; } + line.stateAfter = context.save(!resetState); + line.styles = result.styles; + if (result.classes) { line.styleClasses = result.classes; } + else if (line.styleClasses) { line.styleClasses = null; } + if (updateFrontier === cm.doc.highlightFrontier) + { cm.doc.modeFrontier = Math.max(cm.doc.modeFrontier, ++cm.doc.highlightFrontier); } + } + return line.styles + } + + function getContextBefore(cm, n, precise) { + var doc = cm.doc, display = cm.display; + if (!doc.mode.startState) { return new Context(doc, true, n) } + var start = findStartLine(cm, n, precise); + var saved = start > doc.first && getLine(doc, start - 1).stateAfter; + var context = saved ? Context.fromSaved(doc, saved, start) : new Context(doc, startState(doc.mode), start); + + doc.iter(start, n, function (line) { + processLine(cm, line.text, context); + var pos = context.line; + line.stateAfter = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo ? context.save() : null; + context.nextLine(); + }); + if (precise) { doc.modeFrontier = context.line; } + return context + } + + // Lightweight form of highlight -- proceed over this line and + // update state, but don't save a style array. Used for lines that + // aren't currently visible. + function processLine(cm, text, context, startAt) { + var mode = cm.doc.mode; + var stream = new StringStream(text, cm.options.tabSize, context); + stream.start = stream.pos = startAt || 0; + if (text == "") { callBlankLine(mode, context.state); } + while (!stream.eol()) { + readToken(mode, stream, context.state); + stream.start = stream.pos; + } + } + + function callBlankLine(mode, state) { + if (mode.blankLine) { return mode.blankLine(state) } + if (!mode.innerMode) { return } + var inner = innerMode(mode, state); + if (inner.mode.blankLine) { return inner.mode.blankLine(inner.state) } + } + + function readToken(mode, stream, state, inner) { + for (var i = 0; i < 10; i++) { + if (inner) { inner[0] = innerMode(mode, state).mode; } + var style = mode.token(stream, state); + if (stream.pos > stream.start) { return style } + } + throw new Error("Mode " + mode.name + " failed to advance stream.") + } + + var Token = function(stream, type, state) { + this.start = stream.start; this.end = stream.pos; + this.string = stream.current(); + this.type = type || null; + this.state = state; + }; + + // Utility for getTokenAt and getLineTokens + function takeToken(cm, pos, precise, asArray) { + var doc = cm.doc, mode = doc.mode, style; + pos = clipPos(doc, pos); + var line = getLine(doc, pos.line), context = getContextBefore(cm, pos.line, precise); + var stream = new StringStream(line.text, cm.options.tabSize, context), tokens; + if (asArray) { tokens = []; } + while ((asArray || stream.pos < pos.ch) && !stream.eol()) { + stream.start = stream.pos; + style = readToken(mode, stream, context.state); + if (asArray) { tokens.push(new Token(stream, style, copyState(doc.mode, context.state))); } + } + return asArray ? tokens : new Token(stream, style, context.state) + } + + function extractLineClasses(type, output) { + if (type) { for (;;) { + var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/); + if (!lineClass) { break } + type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length); + var prop = lineClass[1] ? "bgClass" : "textClass"; + if (output[prop] == null) + { output[prop] = lineClass[2]; } + else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output[prop])) + { output[prop] += " " + lineClass[2]; } + } } + return type + } + + // Run the given mode's parser over a line, calling f for each token. + function runMode(cm, text, mode, context, f, lineClasses, forceToEnd) { + var flattenSpans = mode.flattenSpans; + if (flattenSpans == null) { flattenSpans = cm.options.flattenSpans; } + var curStart = 0, curStyle = null; + var stream = new StringStream(text, cm.options.tabSize, context), style; + var inner = cm.options.addModeClass && [null]; + if (text == "") { extractLineClasses(callBlankLine(mode, context.state), lineClasses); } + while (!stream.eol()) { + if (stream.pos > cm.options.maxHighlightLength) { + flattenSpans = false; + if (forceToEnd) { processLine(cm, text, context, stream.pos); } + stream.pos = text.length; + style = null; + } else { + style = extractLineClasses(readToken(mode, stream, context.state, inner), lineClasses); + } + if (inner) { + var mName = inner[0].name; + if (mName) { style = "m-" + (style ? mName + " " + style : mName); } + } + if (!flattenSpans || curStyle != style) { + while (curStart < stream.start) { + curStart = Math.min(stream.start, curStart + 5000); + f(curStart, curStyle); + } + curStyle = style; + } + stream.start = stream.pos; + } + while (curStart < stream.pos) { + // Webkit seems to refuse to render text nodes longer than 57444 + // characters, and returns inaccurate measurements in nodes + // starting around 5000 chars. + var pos = Math.min(stream.pos, curStart + 5000); + f(pos, curStyle); + curStart = pos; + } + } + + // Finds the line to start with when starting a parse. Tries to + // find a line with a stateAfter, so that it can start with a + // valid state. If that fails, it returns the line with the + // smallest indentation, which tends to need the least context to + // parse correctly. + function findStartLine(cm, n, precise) { + var minindent, minline, doc = cm.doc; + var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100); + for (var search = n; search > lim; --search) { + if (search <= doc.first) { return doc.first } + var line = getLine(doc, search - 1), after = line.stateAfter; + if (after && (!precise || search + (after instanceof SavedContext ? after.lookAhead : 0) <= doc.modeFrontier)) + { return search } + var indented = countColumn(line.text, null, cm.options.tabSize); + if (minline == null || minindent > indented) { + minline = search - 1; + minindent = indented; + } + } + return minline + } + + function retreatFrontier(doc, n) { + doc.modeFrontier = Math.min(doc.modeFrontier, n); + if (doc.highlightFrontier < n - 10) { return } + var start = doc.first; + for (var line = n - 1; line > start; line--) { + var saved = getLine(doc, line).stateAfter; + // change is on 3 + // state on line 1 looked ahead 2 -- so saw 3 + // test 1 + 2 < 3 should cover this + if (saved && (!(saved instanceof SavedContext) || line + saved.lookAhead < n)) { + start = line + 1; + break + } + } + doc.highlightFrontier = Math.min(doc.highlightFrontier, start); + } + + // LINE DATA STRUCTURE + + // Line objects. These hold state related to a line, including + // highlighting info (the styles array). + var Line = function(text, markedSpans, estimateHeight) { + this.text = text; + attachMarkedSpans(this, markedSpans); + this.height = estimateHeight ? estimateHeight(this) : 1; + }; + + Line.prototype.lineNo = function () { return lineNo(this) }; + eventMixin(Line); + + // Change the content (text, markers) of a line. Automatically + // invalidates cached information and tries to re-estimate the + // line's height. + function updateLine(line, text, markedSpans, estimateHeight) { + line.text = text; + if (line.stateAfter) { line.stateAfter = null; } + if (line.styles) { line.styles = null; } + if (line.order != null) { line.order = null; } + detachMarkedSpans(line); + attachMarkedSpans(line, markedSpans); + var estHeight = estimateHeight ? estimateHeight(line) : 1; + if (estHeight != line.height) { updateLineHeight(line, estHeight); } + } + + // Detach a line from the document tree and its markers. + function cleanUpLine(line) { + line.parent = null; + detachMarkedSpans(line); + } + + // Convert a style as returned by a mode (either null, or a string + // containing one or more styles) to a CSS style. This is cached, + // and also looks for line-wide styles. + var styleToClassCache = {}, styleToClassCacheWithMode = {}; + function interpretTokenStyle(style, options) { + if (!style || /^\s*$/.test(style)) { return null } + var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache; + return cache[style] || + (cache[style] = style.replace(/\S+/g, "cm-$&")) + } + + // Render the DOM representation of the text of a line. Also builds + // up a 'line map', which points at the DOM nodes that represent + // specific stretches of text, and is used by the measuring code. + // The returned object contains the DOM node, this map, and + // information about line-wide styles that were set by the mode. + function buildLineContent(cm, lineView) { + // The padding-right forces the element to have a 'border', which + // is needed on Webkit to be able to get line-level bounding + // rectangles for it (in measureChar). + var content = eltP("span", null, null, webkit ? "padding-right: .1px" : null); + var builder = {pre: eltP("pre", [content], "CodeMirror-line"), content: content, + col: 0, pos: 0, cm: cm, + trailingSpace: false, + splitSpaces: cm.getOption("lineWrapping")}; + lineView.measure = {}; + + // Iterate over the logical lines that make up this visual line. + for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) { + var line = i ? lineView.rest[i - 1] : lineView.line, order = (void 0); + builder.pos = 0; + builder.addToken = buildToken; + // Optionally wire in some hacks into the token-rendering + // algorithm, to deal with browser quirks. + if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line, cm.doc.direction))) + { builder.addToken = buildTokenBadBidi(builder.addToken, order); } + builder.map = []; + var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line); + insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate)); + if (line.styleClasses) { + if (line.styleClasses.bgClass) + { builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || ""); } + if (line.styleClasses.textClass) + { builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || ""); } + } + + // Ensure at least a single node is present, for measuring. + if (builder.map.length == 0) + { builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure))); } + + // Store the map and a cache object for the current logical line + if (i == 0) { + lineView.measure.map = builder.map; + lineView.measure.cache = {}; + } else { + (lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map) + ;(lineView.measure.caches || (lineView.measure.caches = [])).push({}); + } + } + + // See issue #2901 + if (webkit) { + var last = builder.content.lastChild; + if (/\bcm-tab\b/.test(last.className) || (last.querySelector && last.querySelector(".cm-tab"))) + { builder.content.className = "cm-tab-wrap-hack"; } + } + + signal(cm, "renderLine", cm, lineView.line, builder.pre); + if (builder.pre.className) + { builder.textClass = joinClasses(builder.pre.className, builder.textClass || ""); } + + return builder + } + + function defaultSpecialCharPlaceholder(ch) { + var token = elt("span", "\u2022", "cm-invalidchar"); + token.title = "\\u" + ch.charCodeAt(0).toString(16); + token.setAttribute("aria-label", token.title); + return token + } + + // Build up the DOM representation for a single token, and add it to + // the line map. Takes care to render special characters separately. + function buildToken(builder, text, style, startStyle, endStyle, css, attributes) { + if (!text) { return } + var displayText = builder.splitSpaces ? splitSpaces(text, builder.trailingSpace) : text; + var special = builder.cm.state.specialChars, mustWrap = false; + var content; + if (!special.test(text)) { + builder.col += text.length; + content = document.createTextNode(displayText); + builder.map.push(builder.pos, builder.pos + text.length, content); + if (ie && ie_version < 9) { mustWrap = true; } + builder.pos += text.length; + } else { + content = document.createDocumentFragment(); + var pos = 0; + while (true) { + special.lastIndex = pos; + var m = special.exec(text); + var skipped = m ? m.index - pos : text.length - pos; + if (skipped) { + var txt = document.createTextNode(displayText.slice(pos, pos + skipped)); + if (ie && ie_version < 9) { content.appendChild(elt("span", [txt])); } + else { content.appendChild(txt); } + builder.map.push(builder.pos, builder.pos + skipped, txt); + builder.col += skipped; + builder.pos += skipped; + } + if (!m) { break } + pos += skipped + 1; + var txt$1 = (void 0); + if (m[0] == "\t") { + var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize; + txt$1 = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); + txt$1.setAttribute("role", "presentation"); + txt$1.setAttribute("cm-text", "\t"); + builder.col += tabWidth; + } else if (m[0] == "\r" || m[0] == "\n") { + txt$1 = content.appendChild(elt("span", m[0] == "\r" ? "\u240d" : "\u2424", "cm-invalidchar")); + txt$1.setAttribute("cm-text", m[0]); + builder.col += 1; + } else { + txt$1 = builder.cm.options.specialCharPlaceholder(m[0]); + txt$1.setAttribute("cm-text", m[0]); + if (ie && ie_version < 9) { content.appendChild(elt("span", [txt$1])); } + else { content.appendChild(txt$1); } + builder.col += 1; + } + builder.map.push(builder.pos, builder.pos + 1, txt$1); + builder.pos++; + } + } + builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32; + if (style || startStyle || endStyle || mustWrap || css) { + var fullStyle = style || ""; + if (startStyle) { fullStyle += startStyle; } + if (endStyle) { fullStyle += endStyle; } + var token = elt("span", [content], fullStyle, css); + if (attributes) { + for (var attr in attributes) { if (attributes.hasOwnProperty(attr) && attr != "style" && attr != "class") + { token.setAttribute(attr, attributes[attr]); } } + } + return builder.content.appendChild(token) + } + builder.content.appendChild(content); + } + + // Change some spaces to NBSP to prevent the browser from collapsing + // trailing spaces at the end of a line when rendering text (issue #1362). + function splitSpaces(text, trailingBefore) { + if (text.length > 1 && !/ /.test(text)) { return text } + var spaceBefore = trailingBefore, result = ""; + for (var i = 0; i < text.length; i++) { + var ch = text.charAt(i); + if (ch == " " && spaceBefore && (i == text.length - 1 || text.charCodeAt(i + 1) == 32)) + { ch = "\u00a0"; } + result += ch; + spaceBefore = ch == " "; + } + return result + } + + // Work around nonsense dimensions being reported for stretches of + // right-to-left text. + function buildTokenBadBidi(inner, order) { + return function (builder, text, style, startStyle, endStyle, css, attributes) { + style = style ? style + " cm-force-border" : "cm-force-border"; + var start = builder.pos, end = start + text.length; + for (;;) { + // Find the part that overlaps with the start of this text + var part = (void 0); + for (var i = 0; i < order.length; i++) { + part = order[i]; + if (part.to > start && part.from <= start) { break } + } + if (part.to >= end) { return inner(builder, text, style, startStyle, endStyle, css, attributes) } + inner(builder, text.slice(0, part.to - start), style, startStyle, null, css, attributes); + startStyle = null; + text = text.slice(part.to - start); + start = part.to; + } + } + } + + function buildCollapsedSpan(builder, size, marker, ignoreWidget) { + var widget = !ignoreWidget && marker.widgetNode; + if (widget) { builder.map.push(builder.pos, builder.pos + size, widget); } + if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) { + if (!widget) + { widget = builder.content.appendChild(document.createElement("span")); } + widget.setAttribute("cm-marker", marker.id); + } + if (widget) { + builder.cm.display.input.setUneditable(widget); + builder.content.appendChild(widget); + } + builder.pos += size; + builder.trailingSpace = false; + } + + // Outputs a number of spans to make up a line, taking highlighting + // and marked text into account. + function insertLineContent(line, builder, styles) { + var spans = line.markedSpans, allText = line.text, at = 0; + if (!spans) { + for (var i$1 = 1; i$1 < styles.length; i$1+=2) + { builder.addToken(builder, allText.slice(at, at = styles[i$1]), interpretTokenStyle(styles[i$1+1], builder.cm.options)); } + return + } + + var len = allText.length, pos = 0, i = 1, text = "", style, css; + var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, collapsed, attributes; + for (;;) { + if (nextChange == pos) { // Update current marker set + spanStyle = spanEndStyle = spanStartStyle = css = ""; + attributes = null; + collapsed = null; nextChange = Infinity; + var foundBookmarks = [], endStyles = (void 0); + for (var j = 0; j < spans.length; ++j) { + var sp = spans[j], m = sp.marker; + if (m.type == "bookmark" && sp.from == pos && m.widgetNode) { + foundBookmarks.push(m); + } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapsed && sp.to == pos && sp.from == pos)) { + if (sp.to != null && sp.to != pos && nextChange > sp.to) { + nextChange = sp.to; + spanEndStyle = ""; + } + if (m.className) { spanStyle += " " + m.className; } + if (m.css) { css = (css ? css + ";" : "") + m.css; } + if (m.startStyle && sp.from == pos) { spanStartStyle += " " + m.startStyle; } + if (m.endStyle && sp.to == nextChange) { (endStyles || (endStyles = [])).push(m.endStyle, sp.to); } + // support for the old title property + // https://github.com/codemirror/CodeMirror/pull/5673 + if (m.title) { (attributes || (attributes = {})).title = m.title; } + if (m.attributes) { + for (var attr in m.attributes) + { (attributes || (attributes = {}))[attr] = m.attributes[attr]; } + } + if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0)) + { collapsed = sp; } + } else if (sp.from > pos && nextChange > sp.from) { + nextChange = sp.from; + } + } + if (endStyles) { for (var j$1 = 0; j$1 < endStyles.length; j$1 += 2) + { if (endStyles[j$1 + 1] == nextChange) { spanEndStyle += " " + endStyles[j$1]; } } } + + if (!collapsed || collapsed.from == pos) { for (var j$2 = 0; j$2 < foundBookmarks.length; ++j$2) + { buildCollapsedSpan(builder, 0, foundBookmarks[j$2]); } } + if (collapsed && (collapsed.from || 0) == pos) { + buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos, + collapsed.marker, collapsed.from == null); + if (collapsed.to == null) { return } + if (collapsed.to == pos) { collapsed = false; } + } + } + if (pos >= len) { break } + + var upto = Math.min(len, nextChange); + while (true) { + if (text) { + var end = pos + text.length; + if (!collapsed) { + var tokenText = end > upto ? text.slice(0, upto - pos) : text; + builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, + spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", css, attributes); + } + if (end >= upto) {text = text.slice(upto - pos); pos = upto; break} + pos = end; + spanStartStyle = ""; + } + text = allText.slice(at, at = styles[i++]); + style = interpretTokenStyle(styles[i++], builder.cm.options); + } + } + } + + + // These objects are used to represent the visible (currently drawn) + // part of the document. A LineView may correspond to multiple + // logical lines, if those are connected by collapsed ranges. + function LineView(doc, line, lineN) { + // The starting line + this.line = line; + // Continuing lines, if any + this.rest = visualLineContinued(line); + // Number of logical lines in this visual line + this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1; + this.node = this.text = null; + this.hidden = lineIsHidden(doc, line); + } + + // Create a range of LineView objects for the given lines. + function buildViewArray(cm, from, to) { + var array = [], nextPos; + for (var pos = from; pos < to; pos = nextPos) { + var view = new LineView(cm.doc, getLine(cm.doc, pos), pos); + nextPos = pos + view.size; + array.push(view); + } + return array + } + + var operationGroup = null; + + function pushOperation(op) { + if (operationGroup) { + operationGroup.ops.push(op); + } else { + op.ownsGroup = operationGroup = { + ops: [op], + delayedCallbacks: [] + }; + } + } + + function fireCallbacksForOps(group) { + // Calls delayed callbacks and cursorActivity handlers until no + // new ones appear + var callbacks = group.delayedCallbacks, i = 0; + do { + for (; i < callbacks.length; i++) + { callbacks[i].call(null); } + for (var j = 0; j < group.ops.length; j++) { + var op = group.ops[j]; + if (op.cursorActivityHandlers) + { while (op.cursorActivityCalled < op.cursorActivityHandlers.length) + { op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.cm); } } + } + } while (i < callbacks.length) + } + + function finishOperation(op, endCb) { + var group = op.ownsGroup; + if (!group) { return } + + try { fireCallbacksForOps(group); } + finally { + operationGroup = null; + endCb(group); + } + } + + var orphanDelayedCallbacks = null; + + // Often, we want to signal events at a point where we are in the + // middle of some work, but don't want the handler to start calling + // other methods on the editor, which might be in an inconsistent + // state or simply not expect any other events to happen. + // signalLater looks whether there are any handlers, and schedules + // them to be executed when the last operation ends, or, if no + // operation is active, when a timeout fires. + function signalLater(emitter, type /*, values...*/) { + var arr = getHandlers(emitter, type); + if (!arr.length) { return } + var args = Array.prototype.slice.call(arguments, 2), list; + if (operationGroup) { + list = operationGroup.delayedCallbacks; + } else if (orphanDelayedCallbacks) { + list = orphanDelayedCallbacks; + } else { + list = orphanDelayedCallbacks = []; + setTimeout(fireOrphanDelayed, 0); + } + var loop = function ( i ) { + list.push(function () { return arr[i].apply(null, args); }); + }; + + for (var i = 0; i < arr.length; ++i) + loop( i ); + } + + function fireOrphanDelayed() { + var delayed = orphanDelayedCallbacks; + orphanDelayedCallbacks = null; + for (var i = 0; i < delayed.length; ++i) { delayed[i](); } + } + + // When an aspect of a line changes, a string is added to + // lineView.changes. This updates the relevant part of the line's + // DOM structure. + function updateLineForChanges(cm, lineView, lineN, dims) { + for (var j = 0; j < lineView.changes.length; j++) { + var type = lineView.changes[j]; + if (type == "text") { updateLineText(cm, lineView); } + else if (type == "gutter") { updateLineGutter(cm, lineView, lineN, dims); } + else if (type == "class") { updateLineClasses(cm, lineView); } + else if (type == "widget") { updateLineWidgets(cm, lineView, dims); } + } + lineView.changes = null; + } + + // Lines with gutter elements, widgets or a background class need to + // be wrapped, and have the extra elements added to the wrapper div + function ensureLineWrapped(lineView) { + if (lineView.node == lineView.text) { + lineView.node = elt("div", null, null, "position: relative"); + if (lineView.text.parentNode) + { lineView.text.parentNode.replaceChild(lineView.node, lineView.text); } + lineView.node.appendChild(lineView.text); + if (ie && ie_version < 8) { lineView.node.style.zIndex = 2; } + } + return lineView.node + } + + function updateLineBackground(cm, lineView) { + var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass; + if (cls) { cls += " CodeMirror-linebackground"; } + if (lineView.background) { + if (cls) { lineView.background.className = cls; } + else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null; } + } else if (cls) { + var wrap = ensureLineWrapped(lineView); + lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild); + cm.display.input.setUneditable(lineView.background); + } + } + + // Wrapper around buildLineContent which will reuse the structure + // in display.externalMeasured when possible. + function getLineContent(cm, lineView) { + var ext = cm.display.externalMeasured; + if (ext && ext.line == lineView.line) { + cm.display.externalMeasured = null; + lineView.measure = ext.measure; + return ext.built + } + return buildLineContent(cm, lineView) + } + + // Redraw the line's text. Interacts with the background and text + // classes because the mode may output tokens that influence these + // classes. + function updateLineText(cm, lineView) { + var cls = lineView.text.className; + var built = getLineContent(cm, lineView); + if (lineView.text == lineView.node) { lineView.node = built.pre; } + lineView.text.parentNode.replaceChild(built.pre, lineView.text); + lineView.text = built.pre; + if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) { + lineView.bgClass = built.bgClass; + lineView.textClass = built.textClass; + updateLineClasses(cm, lineView); + } else if (cls) { + lineView.text.className = cls; + } + } + + function updateLineClasses(cm, lineView) { + updateLineBackground(cm, lineView); + if (lineView.line.wrapClass) + { ensureLineWrapped(lineView).className = lineView.line.wrapClass; } + else if (lineView.node != lineView.text) + { lineView.node.className = ""; } + var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass; + lineView.text.className = textClass || ""; + } + + function updateLineGutter(cm, lineView, lineN, dims) { + if (lineView.gutter) { + lineView.node.removeChild(lineView.gutter); + lineView.gutter = null; + } + if (lineView.gutterBackground) { + lineView.node.removeChild(lineView.gutterBackground); + lineView.gutterBackground = null; + } + if (lineView.line.gutterClass) { + var wrap = ensureLineWrapped(lineView); + lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background " + lineView.line.gutterClass, + ("left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px; width: " + (dims.gutterTotalWidth) + "px")); + cm.display.input.setUneditable(lineView.gutterBackground); + wrap.insertBefore(lineView.gutterBackground, lineView.text); + } + var markers = lineView.line.gutterMarkers; + if (cm.options.lineNumbers || markers) { + var wrap$1 = ensureLineWrapped(lineView); + var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", ("left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px")); + cm.display.input.setUneditable(gutterWrap); + wrap$1.insertBefore(gutterWrap, lineView.text); + if (lineView.line.gutterClass) + { gutterWrap.className += " " + lineView.line.gutterClass; } + if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) + { lineView.lineNumber = gutterWrap.appendChild( + elt("div", lineNumberFor(cm.options, lineN), + "CodeMirror-linenumber CodeMirror-gutter-elt", + ("left: " + (dims.gutterLeft["CodeMirror-linenumbers"]) + "px; width: " + (cm.display.lineNumInnerWidth) + "px"))); } + if (markers) { for (var k = 0; k < cm.options.gutters.length; ++k) { + var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id]; + if (found) + { gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", + ("left: " + (dims.gutterLeft[id]) + "px; width: " + (dims.gutterWidth[id]) + "px"))); } + } } + } + } + + function updateLineWidgets(cm, lineView, dims) { + if (lineView.alignable) { lineView.alignable = null; } + for (var node = lineView.node.firstChild, next = (void 0); node; node = next) { + next = node.nextSibling; + if (node.className == "CodeMirror-linewidget") + { lineView.node.removeChild(node); } + } + insertLineWidgets(cm, lineView, dims); + } + + // Build a line's DOM representation from scratch + function buildLineElement(cm, lineView, lineN, dims) { + var built = getLineContent(cm, lineView); + lineView.text = lineView.node = built.pre; + if (built.bgClass) { lineView.bgClass = built.bgClass; } + if (built.textClass) { lineView.textClass = built.textClass; } + + updateLineClasses(cm, lineView); + updateLineGutter(cm, lineView, lineN, dims); + insertLineWidgets(cm, lineView, dims); + return lineView.node + } + + // A lineView may contain multiple logical lines (when merged by + // collapsed spans). The widgets for all of them need to be drawn. + function insertLineWidgets(cm, lineView, dims) { + insertLineWidgetsFor(cm, lineView.line, lineView, dims, true); + if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++) + { insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false); } } + } + + function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) { + if (!line.widgets) { return } + var wrap = ensureLineWrapped(lineView); + for (var i = 0, ws = line.widgets; i < ws.length; ++i) { + var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget"); + if (!widget.handleMouseEvents) { node.setAttribute("cm-ignore-events", "true"); } + positionLineWidget(widget, node, lineView, dims); + cm.display.input.setUneditable(node); + if (allowAbove && widget.above) + { wrap.insertBefore(node, lineView.gutter || lineView.text); } + else + { wrap.appendChild(node); } + signalLater(widget, "redraw"); + } + } + + function positionLineWidget(widget, node, lineView, dims) { + if (widget.noHScroll) { + (lineView.alignable || (lineView.alignable = [])).push(node); + var width = dims.wrapperWidth; + node.style.left = dims.fixedPos + "px"; + if (!widget.coverGutter) { + width -= dims.gutterTotalWidth; + node.style.paddingLeft = dims.gutterTotalWidth + "px"; + } + node.style.width = width + "px"; + } + if (widget.coverGutter) { + node.style.zIndex = 5; + node.style.position = "relative"; + if (!widget.noHScroll) { node.style.marginLeft = -dims.gutterTotalWidth + "px"; } + } + } + + function widgetHeight(widget) { + if (widget.height != null) { return widget.height } + var cm = widget.doc.cm; + if (!cm) { return 0 } + if (!contains(document.body, widget.node)) { + var parentStyle = "position: relative;"; + if (widget.coverGutter) + { parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;"; } + if (widget.noHScroll) + { parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;"; } + removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle)); + } + return widget.height = widget.node.parentNode.offsetHeight + } + + // Return true when the given mouse event happened in a widget + function eventInWidget(display, e) { + for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { + if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true") || + (n.parentNode == display.sizer && n != display.mover)) + { return true } + } + } + + // POSITION MEASUREMENT + + function paddingTop(display) {return display.lineSpace.offsetTop} + function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight} + function paddingH(display) { + if (display.cachedPaddingH) { return display.cachedPaddingH } + var e = removeChildrenAndAdd(display.measure, elt("pre", "x")); + var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle; + var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)}; + if (!isNaN(data.left) && !isNaN(data.right)) { display.cachedPaddingH = data; } + return data + } + + function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth } + function displayWidth(cm) { + return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth + } + function displayHeight(cm) { + return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight + } + + // Ensure the lineView.wrapping.heights array is populated. This is + // an array of bottom offsets for the lines that make up a drawn + // line. When lineWrapping is on, there might be more than one + // height. + function ensureLineHeights(cm, lineView, rect) { + var wrapping = cm.options.lineWrapping; + var curWidth = wrapping && displayWidth(cm); + if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) { + var heights = lineView.measure.heights = []; + if (wrapping) { + lineView.measure.width = curWidth; + var rects = lineView.text.firstChild.getClientRects(); + for (var i = 0; i < rects.length - 1; i++) { + var cur = rects[i], next = rects[i + 1]; + if (Math.abs(cur.bottom - next.bottom) > 2) + { heights.push((cur.bottom + next.top) / 2 - rect.top); } + } + } + heights.push(rect.bottom - rect.top); + } + } + + // Find a line map (mapping character offsets to text nodes) and a + // measurement cache for the given line number. (A line view might + // contain multiple lines when collapsed ranges are present.) + function mapFromLineView(lineView, line, lineN) { + if (lineView.line == line) + { return {map: lineView.measure.map, cache: lineView.measure.cache} } + for (var i = 0; i < lineView.rest.length; i++) + { if (lineView.rest[i] == line) + { return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]} } } + for (var i$1 = 0; i$1 < lineView.rest.length; i$1++) + { if (lineNo(lineView.rest[i$1]) > lineN) + { return {map: lineView.measure.maps[i$1], cache: lineView.measure.caches[i$1], before: true} } } + } + + // Render a line into the hidden node display.externalMeasured. Used + // when measurement is needed for a line that's not in the viewport. + function updateExternalMeasurement(cm, line) { + line = visualLine(line); + var lineN = lineNo(line); + var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN); + view.lineN = lineN; + var built = view.built = buildLineContent(cm, view); + view.text = built.pre; + removeChildrenAndAdd(cm.display.lineMeasure, built.pre); + return view + } + + // Get a {top, bottom, left, right} box (in line-local coordinates) + // for a given character. + function measureChar(cm, line, ch, bias) { + return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias) + } + + // Find a line view that corresponds to the given line number. + function findViewForLine(cm, lineN) { + if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo) + { return cm.display.view[findViewIndex(cm, lineN)] } + var ext = cm.display.externalMeasured; + if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size) + { return ext } + } + + // Measurement can be split in two steps, the set-up work that + // applies to the whole line, and the measurement of the actual + // character. Functions like coordsChar, that need to do a lot of + // measurements in a row, can thus ensure that the set-up work is + // only done once. + function prepareMeasureForLine(cm, line) { + var lineN = lineNo(line); + var view = findViewForLine(cm, lineN); + if (view && !view.text) { + view = null; + } else if (view && view.changes) { + updateLineForChanges(cm, view, lineN, getDimensions(cm)); + cm.curOp.forceUpdate = true; + } + if (!view) + { view = updateExternalMeasurement(cm, line); } + + var info = mapFromLineView(view, line, lineN); + return { + line: line, view: view, rect: null, + map: info.map, cache: info.cache, before: info.before, + hasHeights: false + } + } + + // Given a prepared measurement object, measures the position of an + // actual character (or fetches it from the cache). + function measureCharPrepared(cm, prepared, ch, bias, varHeight) { + if (prepared.before) { ch = -1; } + var key = ch + (bias || ""), found; + if (prepared.cache.hasOwnProperty(key)) { + found = prepared.cache[key]; + } else { + if (!prepared.rect) + { prepared.rect = prepared.view.text.getBoundingClientRect(); } + if (!prepared.hasHeights) { + ensureLineHeights(cm, prepared.view, prepared.rect); + prepared.hasHeights = true; + } + found = measureCharInner(cm, prepared, ch, bias); + if (!found.bogus) { prepared.cache[key] = found; } + } + return {left: found.left, right: found.right, + top: varHeight ? found.rtop : found.top, + bottom: varHeight ? found.rbottom : found.bottom} + } + + var nullRect = {left: 0, right: 0, top: 0, bottom: 0}; + + function nodeAndOffsetInLineMap(map$$1, ch, bias) { + var node, start, end, collapse, mStart, mEnd; + // First, search the line map for the text node corresponding to, + // or closest to, the target character. + for (var i = 0; i < map$$1.length; i += 3) { + mStart = map$$1[i]; + mEnd = map$$1[i + 1]; + if (ch < mStart) { + start = 0; end = 1; + collapse = "left"; + } else if (ch < mEnd) { + start = ch - mStart; + end = start + 1; + } else if (i == map$$1.length - 3 || ch == mEnd && map$$1[i + 3] > ch) { + end = mEnd - mStart; + start = end - 1; + if (ch >= mEnd) { collapse = "right"; } + } + if (start != null) { + node = map$$1[i + 2]; + if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right")) + { collapse = bias; } + if (bias == "left" && start == 0) + { while (i && map$$1[i - 2] == map$$1[i - 3] && map$$1[i - 1].insertLeft) { + node = map$$1[(i -= 3) + 2]; + collapse = "left"; + } } + if (bias == "right" && start == mEnd - mStart) + { while (i < map$$1.length - 3 && map$$1[i + 3] == map$$1[i + 4] && !map$$1[i + 5].insertLeft) { + node = map$$1[(i += 3) + 2]; + collapse = "right"; + } } + break + } + } + return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd} + } + + function getUsefulRect(rects, bias) { + var rect = nullRect; + if (bias == "left") { for (var i = 0; i < rects.length; i++) { + if ((rect = rects[i]).left != rect.right) { break } + } } else { for (var i$1 = rects.length - 1; i$1 >= 0; i$1--) { + if ((rect = rects[i$1]).left != rect.right) { break } + } } + return rect + } + + function measureCharInner(cm, prepared, ch, bias) { + var place = nodeAndOffsetInLineMap(prepared.map, ch, bias); + var node = place.node, start = place.start, end = place.end, collapse = place.collapse; + + var rect; + if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates. + for (var i$1 = 0; i$1 < 4; i$1++) { // Retry a maximum of 4 times when nonsense rectangles are returned + while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) { --start; } + while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) { ++end; } + if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart) + { rect = node.parentNode.getBoundingClientRect(); } + else + { rect = getUsefulRect(range(node, start, end).getClientRects(), bias); } + if (rect.left || rect.right || start == 0) { break } + end = start; + start = start - 1; + collapse = "right"; + } + if (ie && ie_version < 11) { rect = maybeUpdateRectForZooming(cm.display.measure, rect); } + } else { // If it is a widget, simply get the box for the whole widget. + if (start > 0) { collapse = bias = "right"; } + var rects; + if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1) + { rect = rects[bias == "right" ? rects.length - 1 : 0]; } + else + { rect = node.getBoundingClientRect(); } + } + if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) { + var rSpan = node.parentNode.getClientRects()[0]; + if (rSpan) + { rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom}; } + else + { rect = nullRect; } + } + + var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top; + var mid = (rtop + rbot) / 2; + var heights = prepared.view.measure.heights; + var i = 0; + for (; i < heights.length - 1; i++) + { if (mid < heights[i]) { break } } + var top = i ? heights[i - 1] : 0, bot = heights[i]; + var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left, + right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left, + top: top, bottom: bot}; + if (!rect.left && !rect.right) { result.bogus = true; } + if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot; } + + return result + } + + // Work around problem with bounding client rects on ranges being + // returned incorrectly when zoomed on IE10 and below. + function maybeUpdateRectForZooming(measure, rect) { + if (!window.screen || screen.logicalXDPI == null || + screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure)) + { return rect } + var scaleX = screen.logicalXDPI / screen.deviceXDPI; + var scaleY = screen.logicalYDPI / screen.deviceYDPI; + return {left: rect.left * scaleX, right: rect.right * scaleX, + top: rect.top * scaleY, bottom: rect.bottom * scaleY} + } + + function clearLineMeasurementCacheFor(lineView) { + if (lineView.measure) { + lineView.measure.cache = {}; + lineView.measure.heights = null; + if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++) + { lineView.measure.caches[i] = {}; } } + } + } + + function clearLineMeasurementCache(cm) { + cm.display.externalMeasure = null; + removeChildren(cm.display.lineMeasure); + for (var i = 0; i < cm.display.view.length; i++) + { clearLineMeasurementCacheFor(cm.display.view[i]); } + } + + function clearCaches(cm) { + clearLineMeasurementCache(cm); + cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null; + if (!cm.options.lineWrapping) { cm.display.maxLineChanged = true; } + cm.display.lineNumChars = null; + } + + function pageScrollX() { + // Work around https://bugs.chromium.org/p/chromium/issues/detail?id=489206 + // which causes page_Offset and bounding client rects to use + // different reference viewports and invalidate our calculations. + if (chrome && android) { return -(document.body.getBoundingClientRect().left - parseInt(getComputedStyle(document.body).marginLeft)) } + return window.pageXOffset || (document.documentElement || document.body).scrollLeft + } + function pageScrollY() { + if (chrome && android) { return -(document.body.getBoundingClientRect().top - parseInt(getComputedStyle(document.body).marginTop)) } + return window.pageYOffset || (document.documentElement || document.body).scrollTop + } + + function widgetTopHeight(lineObj) { + var height = 0; + if (lineObj.widgets) { for (var i = 0; i < lineObj.widgets.length; ++i) { if (lineObj.widgets[i].above) + { height += widgetHeight(lineObj.widgets[i]); } } } + return height + } + + // Converts a {top, bottom, left, right} box from line-local + // coordinates into another coordinate system. Context may be one of + // "line", "div" (display.lineDiv), "local"./null (editor), "window", + // or "page". + function intoCoordSystem(cm, lineObj, rect, context, includeWidgets) { + if (!includeWidgets) { + var height = widgetTopHeight(lineObj); + rect.top += height; rect.bottom += height; + } + if (context == "line") { return rect } + if (!context) { context = "local"; } + var yOff = heightAtLine(lineObj); + if (context == "local") { yOff += paddingTop(cm.display); } + else { yOff -= cm.display.viewOffset; } + if (context == "page" || context == "window") { + var lOff = cm.display.lineSpace.getBoundingClientRect(); + yOff += lOff.top + (context == "window" ? 0 : pageScrollY()); + var xOff = lOff.left + (context == "window" ? 0 : pageScrollX()); + rect.left += xOff; rect.right += xOff; + } + rect.top += yOff; rect.bottom += yOff; + return rect + } + + // Coverts a box from "div" coords to another coordinate system. + // Context may be "window", "page", "div", or "local"./null. + function fromCoordSystem(cm, coords, context) { + if (context == "div") { return coords } + var left = coords.left, top = coords.top; + // First move into "page" coordinate system + if (context == "page") { + left -= pageScrollX(); + top -= pageScrollY(); + } else if (context == "local" || !context) { + var localBox = cm.display.sizer.getBoundingClientRect(); + left += localBox.left; + top += localBox.top; + } + + var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect(); + return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top} + } + + function charCoords(cm, pos, context, lineObj, bias) { + if (!lineObj) { lineObj = getLine(cm.doc, pos.line); } + return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context) + } + + // Returns a box for a given cursor position, which may have an + // 'other' property containing the position of the secondary cursor + // on a bidi boundary. + // A cursor Pos(line, char, "before") is on the same visual line as `char - 1` + // and after `char - 1` in writing order of `char - 1` + // A cursor Pos(line, char, "after") is on the same visual line as `char` + // and before `char` in writing order of `char` + // Examples (upper-case letters are RTL, lower-case are LTR): + // Pos(0, 1, ...) + // before after + // ab a|b a|b + // aB a|B aB| + // Ab |Ab A|b + // AB B|A B|A + // Every position after the last character on a line is considered to stick + // to the last character on the line. + function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) { + lineObj = lineObj || getLine(cm.doc, pos.line); + if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj); } + function get(ch, right) { + var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight); + if (right) { m.left = m.right; } else { m.right = m.left; } + return intoCoordSystem(cm, lineObj, m, context) + } + var order = getOrder(lineObj, cm.doc.direction), ch = pos.ch, sticky = pos.sticky; + if (ch >= lineObj.text.length) { + ch = lineObj.text.length; + sticky = "before"; + } else if (ch <= 0) { + ch = 0; + sticky = "after"; + } + if (!order) { return get(sticky == "before" ? ch - 1 : ch, sticky == "before") } + + function getBidi(ch, partPos, invert) { + var part = order[partPos], right = part.level == 1; + return get(invert ? ch - 1 : ch, right != invert) + } + var partPos = getBidiPartAt(order, ch, sticky); + var other = bidiOther; + var val = getBidi(ch, partPos, sticky == "before"); + if (other != null) { val.other = getBidi(ch, other, sticky != "before"); } + return val + } + + // Used to cheaply estimate the coordinates for a position. Used for + // intermediate scroll updates. + function estimateCoords(cm, pos) { + var left = 0; + pos = clipPos(cm.doc, pos); + if (!cm.options.lineWrapping) { left = charWidth(cm.display) * pos.ch; } + var lineObj = getLine(cm.doc, pos.line); + var top = heightAtLine(lineObj) + paddingTop(cm.display); + return {left: left, right: left, top: top, bottom: top + lineObj.height} + } + + // Positions returned by coordsChar contain some extra information. + // xRel is the relative x position of the input coordinates compared + // to the found position (so xRel > 0 means the coordinates are to + // the right of the character position, for example). When outside + // is true, that means the coordinates lie outside the line's + // vertical range. + function PosWithInfo(line, ch, sticky, outside, xRel) { + var pos = Pos(line, ch, sticky); + pos.xRel = xRel; + if (outside) { pos.outside = true; } + return pos + } + + // Compute the character position closest to the given coordinates. + // Input must be lineSpace-local ("div" coordinate system). + function coordsChar(cm, x, y) { + var doc = cm.doc; + y += cm.display.viewOffset; + if (y < 0) { return PosWithInfo(doc.first, 0, null, true, -1) } + var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1; + if (lineN > last) + { return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, null, true, 1) } + if (x < 0) { x = 0; } + + var lineObj = getLine(doc, lineN); + for (;;) { + var found = coordsCharInner(cm, lineObj, lineN, x, y); + var collapsed = collapsedSpanAround(lineObj, found.ch + (found.xRel > 0 ? 1 : 0)); + if (!collapsed) { return found } + var rangeEnd = collapsed.find(1); + if (rangeEnd.line == lineN) { return rangeEnd } + lineObj = getLine(doc, lineN = rangeEnd.line); + } + } + + function wrappedLineExtent(cm, lineObj, preparedMeasure, y) { + y -= widgetTopHeight(lineObj); + var end = lineObj.text.length; + var begin = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch - 1).bottom <= y; }, end, 0); + end = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch).top > y; }, begin, end); + return {begin: begin, end: end} + } + + function wrappedLineExtentChar(cm, lineObj, preparedMeasure, target) { + if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj); } + var targetTop = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, target), "line").top; + return wrappedLineExtent(cm, lineObj, preparedMeasure, targetTop) + } + + // Returns true if the given side of a box is after the given + // coordinates, in top-to-bottom, left-to-right order. + function boxIsAfter(box, x, y, left) { + return box.bottom <= y ? false : box.top > y ? true : (left ? box.left : box.right) > x + } + + function coordsCharInner(cm, lineObj, lineNo$$1, x, y) { + // Move y into line-local coordinate space + y -= heightAtLine(lineObj); + var preparedMeasure = prepareMeasureForLine(cm, lineObj); + // When directly calling `measureCharPrepared`, we have to adjust + // for the widgets at this line. + var widgetHeight$$1 = widgetTopHeight(lineObj); + var begin = 0, end = lineObj.text.length, ltr = true; + + var order = getOrder(lineObj, cm.doc.direction); + // If the line isn't plain left-to-right text, first figure out + // which bidi section the coordinates fall into. + if (order) { + var part = (cm.options.lineWrapping ? coordsBidiPartWrapped : coordsBidiPart) + (cm, lineObj, lineNo$$1, preparedMeasure, order, x, y); + ltr = part.level != 1; + // The awkward -1 offsets are needed because findFirst (called + // on these below) will treat its first bound as inclusive, + // second as exclusive, but we want to actually address the + // characters in the part's range + begin = ltr ? part.from : part.to - 1; + end = ltr ? part.to : part.from - 1; + } + + // A binary search to find the first character whose bounding box + // starts after the coordinates. If we run across any whose box wrap + // the coordinates, store that. + var chAround = null, boxAround = null; + var ch = findFirst(function (ch) { + var box = measureCharPrepared(cm, preparedMeasure, ch); + box.top += widgetHeight$$1; box.bottom += widgetHeight$$1; + if (!boxIsAfter(box, x, y, false)) { return false } + if (box.top <= y && box.left <= x) { + chAround = ch; + boxAround = box; + } + return true + }, begin, end); + + var baseX, sticky, outside = false; + // If a box around the coordinates was found, use that + if (boxAround) { + // Distinguish coordinates nearer to the left or right side of the box + var atLeft = x - boxAround.left < boxAround.right - x, atStart = atLeft == ltr; + ch = chAround + (atStart ? 0 : 1); + sticky = atStart ? "after" : "before"; + baseX = atLeft ? boxAround.left : boxAround.right; + } else { + // (Adjust for extended bound, if necessary.) + if (!ltr && (ch == end || ch == begin)) { ch++; } + // To determine which side to associate with, get the box to the + // left of the character and compare it's vertical position to the + // coordinates + sticky = ch == 0 ? "after" : ch == lineObj.text.length ? "before" : + (measureCharPrepared(cm, preparedMeasure, ch - (ltr ? 1 : 0)).bottom + widgetHeight$$1 <= y) == ltr ? + "after" : "before"; + // Now get accurate coordinates for this place, in order to get a + // base X position + var coords = cursorCoords(cm, Pos(lineNo$$1, ch, sticky), "line", lineObj, preparedMeasure); + baseX = coords.left; + outside = y < coords.top || y >= coords.bottom; + } + + ch = skipExtendingChars(lineObj.text, ch, 1); + return PosWithInfo(lineNo$$1, ch, sticky, outside, x - baseX) + } + + function coordsBidiPart(cm, lineObj, lineNo$$1, preparedMeasure, order, x, y) { + // Bidi parts are sorted left-to-right, and in a non-line-wrapping + // situation, we can take this ordering to correspond to the visual + // ordering. This finds the first part whose end is after the given + // coordinates. + var index = findFirst(function (i) { + var part = order[i], ltr = part.level != 1; + return boxIsAfter(cursorCoords(cm, Pos(lineNo$$1, ltr ? part.to : part.from, ltr ? "before" : "after"), + "line", lineObj, preparedMeasure), x, y, true) + }, 0, order.length - 1); + var part = order[index]; + // If this isn't the first part, the part's start is also after + // the coordinates, and the coordinates aren't on the same line as + // that start, move one part back. + if (index > 0) { + var ltr = part.level != 1; + var start = cursorCoords(cm, Pos(lineNo$$1, ltr ? part.from : part.to, ltr ? "after" : "before"), + "line", lineObj, preparedMeasure); + if (boxIsAfter(start, x, y, true) && start.top > y) + { part = order[index - 1]; } + } + return part + } + + function coordsBidiPartWrapped(cm, lineObj, _lineNo, preparedMeasure, order, x, y) { + // In a wrapped line, rtl text on wrapping boundaries can do things + // that don't correspond to the ordering in our `order` array at + // all, so a binary search doesn't work, and we want to return a + // part that only spans one line so that the binary search in + // coordsCharInner is safe. As such, we first find the extent of the + // wrapped line, and then do a flat search in which we discard any + // spans that aren't on the line. + var ref = wrappedLineExtent(cm, lineObj, preparedMeasure, y); + var begin = ref.begin; + var end = ref.end; + if (/\s/.test(lineObj.text.charAt(end - 1))) { end--; } + var part = null, closestDist = null; + for (var i = 0; i < order.length; i++) { + var p = order[i]; + if (p.from >= end || p.to <= begin) { continue } + var ltr = p.level != 1; + var endX = measureCharPrepared(cm, preparedMeasure, ltr ? Math.min(end, p.to) - 1 : Math.max(begin, p.from)).right; + // Weigh against spans ending before this, so that they are only + // picked if nothing ends after + var dist = endX < x ? x - endX + 1e9 : endX - x; + if (!part || closestDist > dist) { + part = p; + closestDist = dist; + } + } + if (!part) { part = order[order.length - 1]; } + // Clip the part to the wrapped line. + if (part.from < begin) { part = {from: begin, to: part.to, level: part.level}; } + if (part.to > end) { part = {from: part.from, to: end, level: part.level}; } + return part + } + + var measureText; + // Compute the default text height. + function textHeight(display) { + if (display.cachedTextHeight != null) { return display.cachedTextHeight } + if (measureText == null) { + measureText = elt("pre"); + // Measure a bunch of lines, for browsers that compute + // fractional heights. + for (var i = 0; i < 49; ++i) { + measureText.appendChild(document.createTextNode("x")); + measureText.appendChild(elt("br")); + } + measureText.appendChild(document.createTextNode("x")); + } + removeChildrenAndAdd(display.measure, measureText); + var height = measureText.offsetHeight / 50; + if (height > 3) { display.cachedTextHeight = height; } + removeChildren(display.measure); + return height || 1 + } + + // Compute the default character width. + function charWidth(display) { + if (display.cachedCharWidth != null) { return display.cachedCharWidth } + var anchor = elt("span", "xxxxxxxxxx"); + var pre = elt("pre", [anchor]); + removeChildrenAndAdd(display.measure, pre); + var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10; + if (width > 2) { display.cachedCharWidth = width; } + return width || 10 + } + + // Do a bulk-read of the DOM positions and sizes needed to draw the + // view, so that we don't interleave reading and writing to the DOM. + function getDimensions(cm) { + var d = cm.display, left = {}, width = {}; + var gutterLeft = d.gutters.clientLeft; + for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { + left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft; + width[cm.options.gutters[i]] = n.clientWidth; + } + return {fixedPos: compensateForHScroll(d), + gutterTotalWidth: d.gutters.offsetWidth, + gutterLeft: left, + gutterWidth: width, + wrapperWidth: d.wrapper.clientWidth} + } + + // Computes display.scroller.scrollLeft + display.gutters.offsetWidth, + // but using getBoundingClientRect to get a sub-pixel-accurate + // result. + function compensateForHScroll(display) { + return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left + } + + // Returns a function that estimates the height of a line, to use as + // first approximation until the line becomes visible (and is thus + // properly measurable). + function estimateHeight(cm) { + var th = textHeight(cm.display), wrapping = cm.options.lineWrapping; + var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3); + return function (line) { + if (lineIsHidden(cm.doc, line)) { return 0 } + + var widgetsHeight = 0; + if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) { + if (line.widgets[i].height) { widgetsHeight += line.widgets[i].height; } + } } + + if (wrapping) + { return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th } + else + { return widgetsHeight + th } + } + } + + function estimateLineHeights(cm) { + var doc = cm.doc, est = estimateHeight(cm); + doc.iter(function (line) { + var estHeight = est(line); + if (estHeight != line.height) { updateLineHeight(line, estHeight); } + }); + } + + // Given a mouse event, find the corresponding position. If liberal + // is false, it checks whether a gutter or scrollbar was clicked, + // and returns null if it was. forRect is used by rectangular + // selections, and tries to estimate a character position even for + // coordinates beyond the right of the text. + function posFromMouse(cm, e, liberal, forRect) { + var display = cm.display; + if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") { return null } + + var x, y, space = display.lineSpace.getBoundingClientRect(); + // Fails unpredictably on IE[67] when mouse is dragged around quickly. + try { x = e.clientX - space.left; y = e.clientY - space.top; } + catch (e) { return null } + var coords = coordsChar(cm, x, y), line; + if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) { + var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length; + coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff)); + } + return coords + } + + // Find the view element corresponding to a given line. Return null + // when the line isn't visible. + function findViewIndex(cm, n) { + if (n >= cm.display.viewTo) { return null } + n -= cm.display.viewFrom; + if (n < 0) { return null } + var view = cm.display.view; + for (var i = 0; i < view.length; i++) { + n -= view[i].size; + if (n < 0) { return i } + } + } + + function updateSelection(cm) { + cm.display.input.showSelection(cm.display.input.prepareSelection()); + } + + function prepareSelection(cm, primary) { + if ( primary === void 0 ) primary = true; + + var doc = cm.doc, result = {}; + var curFragment = result.cursors = document.createDocumentFragment(); + var selFragment = result.selection = document.createDocumentFragment(); + + for (var i = 0; i < doc.sel.ranges.length; i++) { + if (!primary && i == doc.sel.primIndex) { continue } + var range$$1 = doc.sel.ranges[i]; + if (range$$1.from().line >= cm.display.viewTo || range$$1.to().line < cm.display.viewFrom) { continue } + var collapsed = range$$1.empty(); + if (collapsed || cm.options.showCursorWhenSelecting) + { drawSelectionCursor(cm, range$$1.head, curFragment); } + if (!collapsed) + { drawSelectionRange(cm, range$$1, selFragment); } + } + return result + } + + // Draws a cursor for the given range + function drawSelectionCursor(cm, head, output) { + var pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursorHeightPerLine); + + var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor")); + cursor.style.left = pos.left + "px"; + cursor.style.top = pos.top + "px"; + cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px"; + + if (pos.other) { + // Secondary cursor, shown when on a 'jump' in bi-directional text + var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor")); + otherCursor.style.display = ""; + otherCursor.style.left = pos.other.left + "px"; + otherCursor.style.top = pos.other.top + "px"; + otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px"; + } + } + + function cmpCoords(a, b) { return a.top - b.top || a.left - b.left } + + // Draws the given range as a highlighted selection + function drawSelectionRange(cm, range$$1, output) { + var display = cm.display, doc = cm.doc; + var fragment = document.createDocumentFragment(); + var padding = paddingH(cm.display), leftSide = padding.left; + var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right; + var docLTR = doc.direction == "ltr"; + + function add(left, top, width, bottom) { + if (top < 0) { top = 0; } + top = Math.round(top); + bottom = Math.round(bottom); + fragment.appendChild(elt("div", null, "CodeMirror-selected", ("position: absolute; left: " + left + "px;\n top: " + top + "px; width: " + (width == null ? rightSide - left : width) + "px;\n height: " + (bottom - top) + "px"))); + } + + function drawForLine(line, fromArg, toArg) { + var lineObj = getLine(doc, line); + var lineLen = lineObj.text.length; + var start, end; + function coords(ch, bias) { + return charCoords(cm, Pos(line, ch), "div", lineObj, bias) + } + + function wrapX(pos, dir, side) { + var extent = wrappedLineExtentChar(cm, lineObj, null, pos); + var prop = (dir == "ltr") == (side == "after") ? "left" : "right"; + var ch = side == "after" ? extent.begin : extent.end - (/\s/.test(lineObj.text.charAt(extent.end - 1)) ? 2 : 1); + return coords(ch, prop)[prop] + } + + var order = getOrder(lineObj, doc.direction); + iterateBidiSections(order, fromArg || 0, toArg == null ? lineLen : toArg, function (from, to, dir, i) { + var ltr = dir == "ltr"; + var fromPos = coords(from, ltr ? "left" : "right"); + var toPos = coords(to - 1, ltr ? "right" : "left"); + + var openStart = fromArg == null && from == 0, openEnd = toArg == null && to == lineLen; + var first = i == 0, last = !order || i == order.length - 1; + if (toPos.top - fromPos.top <= 3) { // Single line + var openLeft = (docLTR ? openStart : openEnd) && first; + var openRight = (docLTR ? openEnd : openStart) && last; + var left = openLeft ? leftSide : (ltr ? fromPos : toPos).left; + var right = openRight ? rightSide : (ltr ? toPos : fromPos).right; + add(left, fromPos.top, right - left, fromPos.bottom); + } else { // Multiple lines + var topLeft, topRight, botLeft, botRight; + if (ltr) { + topLeft = docLTR && openStart && first ? leftSide : fromPos.left; + topRight = docLTR ? rightSide : wrapX(from, dir, "before"); + botLeft = docLTR ? leftSide : wrapX(to, dir, "after"); + botRight = docLTR && openEnd && last ? rightSide : toPos.right; + } else { + topLeft = !docLTR ? leftSide : wrapX(from, dir, "before"); + topRight = !docLTR && openStart && first ? rightSide : fromPos.right; + botLeft = !docLTR && openEnd && last ? leftSide : toPos.left; + botRight = !docLTR ? rightSide : wrapX(to, dir, "after"); + } + add(topLeft, fromPos.top, topRight - topLeft, fromPos.bottom); + if (fromPos.bottom < toPos.top) { add(leftSide, fromPos.bottom, null, toPos.top); } + add(botLeft, toPos.top, botRight - botLeft, toPos.bottom); + } + + if (!start || cmpCoords(fromPos, start) < 0) { start = fromPos; } + if (cmpCoords(toPos, start) < 0) { start = toPos; } + if (!end || cmpCoords(fromPos, end) < 0) { end = fromPos; } + if (cmpCoords(toPos, end) < 0) { end = toPos; } + }); + return {start: start, end: end} + } + + var sFrom = range$$1.from(), sTo = range$$1.to(); + if (sFrom.line == sTo.line) { + drawForLine(sFrom.line, sFrom.ch, sTo.ch); + } else { + var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line); + var singleVLine = visualLine(fromLine) == visualLine(toLine); + var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end; + var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start; + if (singleVLine) { + if (leftEnd.top < rightStart.top - 2) { + add(leftEnd.right, leftEnd.top, null, leftEnd.bottom); + add(leftSide, rightStart.top, rightStart.left, rightStart.bottom); + } else { + add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom); + } + } + if (leftEnd.bottom < rightStart.top) + { add(leftSide, leftEnd.bottom, null, rightStart.top); } + } + + output.appendChild(fragment); + } + + // Cursor-blinking + function restartBlink(cm) { + if (!cm.state.focused) { return } + var display = cm.display; + clearInterval(display.blinker); + var on = true; + display.cursorDiv.style.visibility = ""; + if (cm.options.cursorBlinkRate > 0) + { display.blinker = setInterval(function () { return display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden"; }, + cm.options.cursorBlinkRate); } + else if (cm.options.cursorBlinkRate < 0) + { display.cursorDiv.style.visibility = "hidden"; } + } + + function ensureFocus(cm) { + if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm); } + } + + function delayBlurEvent(cm) { + cm.state.delayingBlurEvent = true; + setTimeout(function () { if (cm.state.delayingBlurEvent) { + cm.state.delayingBlurEvent = false; + onBlur(cm); + } }, 100); + } + + function onFocus(cm, e) { + if (cm.state.delayingBlurEvent) { cm.state.delayingBlurEvent = false; } + + if (cm.options.readOnly == "nocursor") { return } + if (!cm.state.focused) { + signal(cm, "focus", cm, e); + cm.state.focused = true; + addClass(cm.display.wrapper, "CodeMirror-focused"); + // This test prevents this from firing when a context + // menu is closed (since the input reset would kill the + // select-all detection hack) + if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) { + cm.display.input.reset(); + if (webkit) { setTimeout(function () { return cm.display.input.reset(true); }, 20); } // Issue #1730 + } + cm.display.input.receivedFocus(); + } + restartBlink(cm); + } + function onBlur(cm, e) { + if (cm.state.delayingBlurEvent) { return } + + if (cm.state.focused) { + signal(cm, "blur", cm, e); + cm.state.focused = false; + rmClass(cm.display.wrapper, "CodeMirror-focused"); + } + clearInterval(cm.display.blinker); + setTimeout(function () { if (!cm.state.focused) { cm.display.shift = false; } }, 150); + } + + // Read the actual heights of the rendered lines, and update their + // stored heights to match. + function updateHeightsInViewport(cm) { + var display = cm.display; + var prevBottom = display.lineDiv.offsetTop; + for (var i = 0; i < display.view.length; i++) { + var cur = display.view[i], wrapping = cm.options.lineWrapping; + var height = (void 0), width = 0; + if (cur.hidden) { continue } + if (ie && ie_version < 8) { + var bot = cur.node.offsetTop + cur.node.offsetHeight; + height = bot - prevBottom; + prevBottom = bot; + } else { + var box = cur.node.getBoundingClientRect(); + height = box.bottom - box.top; + // Check that lines don't extend past the right of the current + // editor width + if (!wrapping && cur.text.firstChild) + { width = cur.text.firstChild.getBoundingClientRect().right - box.left - 1; } + } + var diff = cur.line.height - height; + if (diff > .005 || diff < -.005) { + updateLineHeight(cur.line, height); + updateWidgetHeight(cur.line); + if (cur.rest) { for (var j = 0; j < cur.rest.length; j++) + { updateWidgetHeight(cur.rest[j]); } } + } + if (width > cm.display.sizerWidth) { + var chWidth = Math.ceil(width / charWidth(cm.display)); + if (chWidth > cm.display.maxLineLength) { + cm.display.maxLineLength = chWidth; + cm.display.maxLine = cur.line; + cm.display.maxLineChanged = true; + } + } + } + } + + // Read and store the height of line widgets associated with the + // given line. + function updateWidgetHeight(line) { + if (line.widgets) { for (var i = 0; i < line.widgets.length; ++i) { + var w = line.widgets[i], parent = w.node.parentNode; + if (parent) { w.height = parent.offsetHeight; } + } } + } + + // Compute the lines that are visible in a given viewport (defaults + // the the current scroll position). viewport may contain top, + // height, and ensure (see op.scrollToPos) properties. + function visibleLines(display, doc, viewport) { + var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop; + top = Math.floor(top - paddingTop(display)); + var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight; + + var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom); + // Ensure is a {from: {line, ch}, to: {line, ch}} object, and + // forces those lines into the viewport (if possible). + if (viewport && viewport.ensure) { + var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line; + if (ensureFrom < from) { + from = ensureFrom; + to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight); + } else if (Math.min(ensureTo, doc.lastLine()) >= to) { + from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight); + to = ensureTo; + } + } + return {from: from, to: Math.max(to, from + 1)} + } + + // Re-align line numbers and gutter marks to compensate for + // horizontal scrolling. + function alignHorizontally(cm) { + var display = cm.display, view = display.view; + if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) { return } + var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft; + var gutterW = display.gutters.offsetWidth, left = comp + "px"; + for (var i = 0; i < view.length; i++) { if (!view[i].hidden) { + if (cm.options.fixedGutter) { + if (view[i].gutter) + { view[i].gutter.style.left = left; } + if (view[i].gutterBackground) + { view[i].gutterBackground.style.left = left; } + } + var align = view[i].alignable; + if (align) { for (var j = 0; j < align.length; j++) + { align[j].style.left = left; } } + } } + if (cm.options.fixedGutter) + { display.gutters.style.left = (comp + gutterW) + "px"; } + } + + // Used to ensure that the line number gutter is still the right + // size for the current document size. Returns true when an update + // is needed. + function maybeUpdateLineNumberWidth(cm) { + if (!cm.options.lineNumbers) { return false } + var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display; + if (last.length != display.lineNumChars) { + var test = display.measure.appendChild(elt("div", [elt("div", last)], + "CodeMirror-linenumber CodeMirror-gutter-elt")); + var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW; + display.lineGutter.style.width = ""; + display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1; + display.lineNumWidth = display.lineNumInnerWidth + padding; + display.lineNumChars = display.lineNumInnerWidth ? last.length : -1; + display.lineGutter.style.width = display.lineNumWidth + "px"; + updateGutterSpace(cm); + return true + } + return false + } + + // SCROLLING THINGS INTO VIEW + + // If an editor sits on the top or bottom of the window, partially + // scrolled out of view, this ensures that the cursor is visible. + function maybeScrollWindow(cm, rect) { + if (signalDOMEvent(cm, "scrollCursorIntoView")) { return } + + var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null; + if (rect.top + box.top < 0) { doScroll = true; } + else if (rect.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) { doScroll = false; } + if (doScroll != null && !phantom) { + var scrollNode = elt("div", "\u200b", null, ("position: absolute;\n top: " + (rect.top - display.viewOffset - paddingTop(cm.display)) + "px;\n height: " + (rect.bottom - rect.top + scrollGap(cm) + display.barHeight) + "px;\n left: " + (rect.left) + "px; width: " + (Math.max(2, rect.right - rect.left)) + "px;")); + cm.display.lineSpace.appendChild(scrollNode); + scrollNode.scrollIntoView(doScroll); + cm.display.lineSpace.removeChild(scrollNode); + } + } + + // Scroll a given position into view (immediately), verifying that + // it actually became visible (as line heights are accurately + // measured, the position of something may 'drift' during drawing). + function scrollPosIntoView(cm, pos, end, margin) { + if (margin == null) { margin = 0; } + var rect; + if (!cm.options.lineWrapping && pos == end) { + // Set pos and end to the cursor positions around the character pos sticks to + // If pos.sticky == "before", that is around pos.ch - 1, otherwise around pos.ch + // If pos == Pos(_, 0, "before"), pos and end are unchanged + pos = pos.ch ? Pos(pos.line, pos.sticky == "before" ? pos.ch - 1 : pos.ch, "after") : pos; + end = pos.sticky == "before" ? Pos(pos.line, pos.ch + 1, "before") : pos; + } + for (var limit = 0; limit < 5; limit++) { + var changed = false; + var coords = cursorCoords(cm, pos); + var endCoords = !end || end == pos ? coords : cursorCoords(cm, end); + rect = {left: Math.min(coords.left, endCoords.left), + top: Math.min(coords.top, endCoords.top) - margin, + right: Math.max(coords.left, endCoords.left), + bottom: Math.max(coords.bottom, endCoords.bottom) + margin}; + var scrollPos = calculateScrollPos(cm, rect); + var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft; + if (scrollPos.scrollTop != null) { + updateScrollTop(cm, scrollPos.scrollTop); + if (Math.abs(cm.doc.scrollTop - startTop) > 1) { changed = true; } + } + if (scrollPos.scrollLeft != null) { + setScrollLeft(cm, scrollPos.scrollLeft); + if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) { changed = true; } + } + if (!changed) { break } + } + return rect + } + + // Scroll a given set of coordinates into view (immediately). + function scrollIntoView(cm, rect) { + var scrollPos = calculateScrollPos(cm, rect); + if (scrollPos.scrollTop != null) { updateScrollTop(cm, scrollPos.scrollTop); } + if (scrollPos.scrollLeft != null) { setScrollLeft(cm, scrollPos.scrollLeft); } + } + + // Calculate a new scroll position needed to scroll the given + // rectangle into view. Returns an object with scrollTop and + // scrollLeft properties. When these are undefined, the + // vertical/horizontal position does not need to be adjusted. + function calculateScrollPos(cm, rect) { + var display = cm.display, snapMargin = textHeight(cm.display); + if (rect.top < 0) { rect.top = 0; } + var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop; + var screen = displayHeight(cm), result = {}; + if (rect.bottom - rect.top > screen) { rect.bottom = rect.top + screen; } + var docBottom = cm.doc.height + paddingVert(display); + var atTop = rect.top < snapMargin, atBottom = rect.bottom > docBottom - snapMargin; + if (rect.top < screentop) { + result.scrollTop = atTop ? 0 : rect.top; + } else if (rect.bottom > screentop + screen) { + var newTop = Math.min(rect.top, (atBottom ? docBottom : rect.bottom) - screen); + if (newTop != screentop) { result.scrollTop = newTop; } + } + + var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft; + var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0); + var tooWide = rect.right - rect.left > screenw; + if (tooWide) { rect.right = rect.left + screenw; } + if (rect.left < 10) + { result.scrollLeft = 0; } + else if (rect.left < screenleft) + { result.scrollLeft = Math.max(0, rect.left - (tooWide ? 0 : 10)); } + else if (rect.right > screenw + screenleft - 3) + { result.scrollLeft = rect.right + (tooWide ? 0 : 10) - screenw; } + return result + } + + // Store a relative adjustment to the scroll position in the current + // operation (to be applied when the operation finishes). + function addToScrollTop(cm, top) { + if (top == null) { return } + resolveScrollToPos(cm); + cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top; + } + + // Make sure that at the end of the operation the current cursor is + // shown. + function ensureCursorVisible(cm) { + resolveScrollToPos(cm); + var cur = cm.getCursor(); + cm.curOp.scrollToPos = {from: cur, to: cur, margin: cm.options.cursorScrollMargin}; + } + + function scrollToCoords(cm, x, y) { + if (x != null || y != null) { resolveScrollToPos(cm); } + if (x != null) { cm.curOp.scrollLeft = x; } + if (y != null) { cm.curOp.scrollTop = y; } + } + + function scrollToRange(cm, range$$1) { + resolveScrollToPos(cm); + cm.curOp.scrollToPos = range$$1; + } + + // When an operation has its scrollToPos property set, and another + // scroll action is applied before the end of the operation, this + // 'simulates' scrolling that position into view in a cheap way, so + // that the effect of intermediate scroll commands is not ignored. + function resolveScrollToPos(cm) { + var range$$1 = cm.curOp.scrollToPos; + if (range$$1) { + cm.curOp.scrollToPos = null; + var from = estimateCoords(cm, range$$1.from), to = estimateCoords(cm, range$$1.to); + scrollToCoordsRange(cm, from, to, range$$1.margin); + } + } + + function scrollToCoordsRange(cm, from, to, margin) { + var sPos = calculateScrollPos(cm, { + left: Math.min(from.left, to.left), + top: Math.min(from.top, to.top) - margin, + right: Math.max(from.right, to.right), + bottom: Math.max(from.bottom, to.bottom) + margin + }); + scrollToCoords(cm, sPos.scrollLeft, sPos.scrollTop); + } + + // Sync the scrollable area and scrollbars, ensure the viewport + // covers the visible area. + function updateScrollTop(cm, val) { + if (Math.abs(cm.doc.scrollTop - val) < 2) { return } + if (!gecko) { updateDisplaySimple(cm, {top: val}); } + setScrollTop(cm, val, true); + if (gecko) { updateDisplaySimple(cm); } + startWorker(cm, 100); + } + + function setScrollTop(cm, val, forceScroll) { + val = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight, val); + if (cm.display.scroller.scrollTop == val && !forceScroll) { return } + cm.doc.scrollTop = val; + cm.display.scrollbars.setScrollTop(val); + if (cm.display.scroller.scrollTop != val) { cm.display.scroller.scrollTop = val; } + } + + // Sync scroller and scrollbar, ensure the gutter elements are + // aligned. + function setScrollLeft(cm, val, isScroller, forceScroll) { + val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth); + if ((isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) && !forceScroll) { return } + cm.doc.scrollLeft = val; + alignHorizontally(cm); + if (cm.display.scroller.scrollLeft != val) { cm.display.scroller.scrollLeft = val; } + cm.display.scrollbars.setScrollLeft(val); + } + + // SCROLLBARS + + // Prepare DOM reads needed to update the scrollbars. Done in one + // shot to minimize update/measure roundtrips. + function measureForScrollbars(cm) { + var d = cm.display, gutterW = d.gutters.offsetWidth; + var docH = Math.round(cm.doc.height + paddingVert(cm.display)); + return { + clientHeight: d.scroller.clientHeight, + viewHeight: d.wrapper.clientHeight, + scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth, + viewWidth: d.wrapper.clientWidth, + barLeft: cm.options.fixedGutter ? gutterW : 0, + docHeight: docH, + scrollHeight: docH + scrollGap(cm) + d.barHeight, + nativeBarWidth: d.nativeBarWidth, + gutterWidth: gutterW + } + } + + var NativeScrollbars = function(place, scroll, cm) { + this.cm = cm; + var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar"); + var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar"); + vert.tabIndex = horiz.tabIndex = -1; + place(vert); place(horiz); + + on(vert, "scroll", function () { + if (vert.clientHeight) { scroll(vert.scrollTop, "vertical"); } + }); + on(horiz, "scroll", function () { + if (horiz.clientWidth) { scroll(horiz.scrollLeft, "horizontal"); } + }); + + this.checkedZeroWidth = false; + // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). + if (ie && ie_version < 8) { this.horiz.style.minHeight = this.vert.style.minWidth = "18px"; } + }; + + NativeScrollbars.prototype.update = function (measure) { + var needsH = measure.scrollWidth > measure.clientWidth + 1; + var needsV = measure.scrollHeight > measure.clientHeight + 1; + var sWidth = measure.nativeBarWidth; + + if (needsV) { + this.vert.style.display = "block"; + this.vert.style.bottom = needsH ? sWidth + "px" : "0"; + var totalHeight = measure.viewHeight - (needsH ? sWidth : 0); + // A bug in IE8 can cause this value to be negative, so guard it. + this.vert.firstChild.style.height = + Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px"; + } else { + this.vert.style.display = ""; + this.vert.firstChild.style.height = "0"; + } + + if (needsH) { + this.horiz.style.display = "block"; + this.horiz.style.right = needsV ? sWidth + "px" : "0"; + this.horiz.style.left = measure.barLeft + "px"; + var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0); + this.horiz.firstChild.style.width = + Math.max(0, measure.scrollWidth - measure.clientWidth + totalWidth) + "px"; + } else { + this.horiz.style.display = ""; + this.horiz.firstChild.style.width = "0"; + } + + if (!this.checkedZeroWidth && measure.clientHeight > 0) { + if (sWidth == 0) { this.zeroWidthHack(); } + this.checkedZeroWidth = true; + } + + return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0} + }; + + NativeScrollbars.prototype.setScrollLeft = function (pos) { + if (this.horiz.scrollLeft != pos) { this.horiz.scrollLeft = pos; } + if (this.disableHoriz) { this.enableZeroWidthBar(this.horiz, this.disableHoriz, "horiz"); } + }; + + NativeScrollbars.prototype.setScrollTop = function (pos) { + if (this.vert.scrollTop != pos) { this.vert.scrollTop = pos; } + if (this.disableVert) { this.enableZeroWidthBar(this.vert, this.disableVert, "vert"); } + }; + + NativeScrollbars.prototype.zeroWidthHack = function () { + var w = mac && !mac_geMountainLion ? "12px" : "18px"; + this.horiz.style.height = this.vert.style.width = w; + this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none"; + this.disableHoriz = new Delayed; + this.disableVert = new Delayed; + }; + + NativeScrollbars.prototype.enableZeroWidthBar = function (bar, delay, type) { + bar.style.pointerEvents = "auto"; + function maybeDisable() { + // To find out whether the scrollbar is still visible, we + // check whether the element under the pixel in the bottom + // right corner of the scrollbar box is the scrollbar box + // itself (when the bar is still visible) or its filler child + // (when the bar is hidden). If it is still visible, we keep + // it enabled, if it's hidden, we disable pointer events. + var box = bar.getBoundingClientRect(); + var elt$$1 = type == "vert" ? document.elementFromPoint(box.right - 1, (box.top + box.bottom) / 2) + : document.elementFromPoint((box.right + box.left) / 2, box.bottom - 1); + if (elt$$1 != bar) { bar.style.pointerEvents = "none"; } + else { delay.set(1000, maybeDisable); } + } + delay.set(1000, maybeDisable); + }; + + NativeScrollbars.prototype.clear = function () { + var parent = this.horiz.parentNode; + parent.removeChild(this.horiz); + parent.removeChild(this.vert); + }; + + var NullScrollbars = function () {}; + + NullScrollbars.prototype.update = function () { return {bottom: 0, right: 0} }; + NullScrollbars.prototype.setScrollLeft = function () {}; + NullScrollbars.prototype.setScrollTop = function () {}; + NullScrollbars.prototype.clear = function () {}; + + function updateScrollbars(cm, measure) { + if (!measure) { measure = measureForScrollbars(cm); } + var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight; + updateScrollbarsInner(cm, measure); + for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) { + if (startWidth != cm.display.barWidth && cm.options.lineWrapping) + { updateHeightsInViewport(cm); } + updateScrollbarsInner(cm, measureForScrollbars(cm)); + startWidth = cm.display.barWidth; startHeight = cm.display.barHeight; + } + } + + // Re-synchronize the fake scrollbars with the actual size of the + // content. + function updateScrollbarsInner(cm, measure) { + var d = cm.display; + var sizes = d.scrollbars.update(measure); + + d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px"; + d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px"; + d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent"; + + if (sizes.right && sizes.bottom) { + d.scrollbarFiller.style.display = "block"; + d.scrollbarFiller.style.height = sizes.bottom + "px"; + d.scrollbarFiller.style.width = sizes.right + "px"; + } else { d.scrollbarFiller.style.display = ""; } + if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { + d.gutterFiller.style.display = "block"; + d.gutterFiller.style.height = sizes.bottom + "px"; + d.gutterFiller.style.width = measure.gutterWidth + "px"; + } else { d.gutterFiller.style.display = ""; } + } + + var scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars}; + + function initScrollbars(cm) { + if (cm.display.scrollbars) { + cm.display.scrollbars.clear(); + if (cm.display.scrollbars.addClass) + { rmClass(cm.display.wrapper, cm.display.scrollbars.addClass); } + } + + cm.display.scrollbars = new scrollbarModel[cm.options.scrollbarStyle](function (node) { + cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller); + // Prevent clicks in the scrollbars from killing focus + on(node, "mousedown", function () { + if (cm.state.focused) { setTimeout(function () { return cm.display.input.focus(); }, 0); } + }); + node.setAttribute("cm-not-content", "true"); + }, function (pos, axis) { + if (axis == "horizontal") { setScrollLeft(cm, pos); } + else { updateScrollTop(cm, pos); } + }, cm); + if (cm.display.scrollbars.addClass) + { addClass(cm.display.wrapper, cm.display.scrollbars.addClass); } + } + + // Operations are used to wrap a series of changes to the editor + // state in such a way that each change won't have to update the + // cursor and display (which would be awkward, slow, and + // error-prone). Instead, display updates are batched and then all + // combined and executed at once. + + var nextOpId = 0; + // Start a new operation. + function startOperation(cm) { + cm.curOp = { + cm: cm, + viewChanged: false, // Flag that indicates that lines might need to be redrawn + startHeight: cm.doc.height, // Used to detect need to update scrollbar + forceUpdate: false, // Used to force a redraw + updateInput: 0, // Whether to reset the input textarea + typing: false, // Whether this reset should be careful to leave existing text (for compositing) + changeObjs: null, // Accumulated changes, for firing change events + cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on + cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already + selectionChanged: false, // Whether the selection needs to be redrawn + updateMaxLine: false, // Set when the widest line needs to be determined anew + scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet + scrollToPos: null, // Used to scroll to a specific position + focus: false, + id: ++nextOpId // Unique ID + }; + pushOperation(cm.curOp); + } + + // Finish an operation, updating the display and signalling delayed events + function endOperation(cm) { + var op = cm.curOp; + if (op) { finishOperation(op, function (group) { + for (var i = 0; i < group.ops.length; i++) + { group.ops[i].cm.curOp = null; } + endOperations(group); + }); } + } + + // The DOM updates done when an operation finishes are batched so + // that the minimum number of relayouts are required. + function endOperations(group) { + var ops = group.ops; + for (var i = 0; i < ops.length; i++) // Read DOM + { endOperation_R1(ops[i]); } + for (var i$1 = 0; i$1 < ops.length; i$1++) // Write DOM (maybe) + { endOperation_W1(ops[i$1]); } + for (var i$2 = 0; i$2 < ops.length; i$2++) // Read DOM + { endOperation_R2(ops[i$2]); } + for (var i$3 = 0; i$3 < ops.length; i$3++) // Write DOM (maybe) + { endOperation_W2(ops[i$3]); } + for (var i$4 = 0; i$4 < ops.length; i$4++) // Read DOM + { endOperation_finish(ops[i$4]); } + } + + function endOperation_R1(op) { + var cm = op.cm, display = cm.display; + maybeClipScrollbars(cm); + if (op.updateMaxLine) { findMaxLine(cm); } + + op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null || + op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom || + op.scrollToPos.to.line >= display.viewTo) || + display.maxLineChanged && cm.options.lineWrapping; + op.update = op.mustUpdate && + new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate); + } + + function endOperation_W1(op) { + op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update); + } + + function endOperation_R2(op) { + var cm = op.cm, display = cm.display; + if (op.updatedDisplay) { updateHeightsInViewport(cm); } + + op.barMeasure = measureForScrollbars(cm); + + // If the max line changed since it was last measured, measure it, + // and ensure the document's width matches it. + // updateDisplay_W2 will use these properties to do the actual resizing + if (display.maxLineChanged && !cm.options.lineWrapping) { + op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3; + cm.display.sizerWidth = op.adjustWidthTo; + op.barMeasure.scrollWidth = + Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth); + op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm)); + } + + if (op.updatedDisplay || op.selectionChanged) + { op.preparedSelection = display.input.prepareSelection(); } + } + + function endOperation_W2(op) { + var cm = op.cm; + + if (op.adjustWidthTo != null) { + cm.display.sizer.style.minWidth = op.adjustWidthTo + "px"; + if (op.maxScrollLeft < cm.doc.scrollLeft) + { setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true); } + cm.display.maxLineChanged = false; + } + + var takeFocus = op.focus && op.focus == activeElt(); + if (op.preparedSelection) + { cm.display.input.showSelection(op.preparedSelection, takeFocus); } + if (op.updatedDisplay || op.startHeight != cm.doc.height) + { updateScrollbars(cm, op.barMeasure); } + if (op.updatedDisplay) + { setDocumentHeight(cm, op.barMeasure); } + + if (op.selectionChanged) { restartBlink(cm); } + + if (cm.state.focused && op.updateInput) + { cm.display.input.reset(op.typing); } + if (takeFocus) { ensureFocus(op.cm); } + } + + function endOperation_finish(op) { + var cm = op.cm, display = cm.display, doc = cm.doc; + + if (op.updatedDisplay) { postUpdateDisplay(cm, op.update); } + + // Abort mouse wheel delta measurement, when scrolling explicitly + if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos)) + { display.wheelStartX = display.wheelStartY = null; } + + // Propagate the scroll position to the actual DOM scroller + if (op.scrollTop != null) { setScrollTop(cm, op.scrollTop, op.forceScroll); } + + if (op.scrollLeft != null) { setScrollLeft(cm, op.scrollLeft, true, true); } + // If we need to scroll a specific position into view, do so. + if (op.scrollToPos) { + var rect = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), + clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin); + maybeScrollWindow(cm, rect); + } + + // Fire events for markers that are hidden/unidden by editing or + // undoing + var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers; + if (hidden) { for (var i = 0; i < hidden.length; ++i) + { if (!hidden[i].lines.length) { signal(hidden[i], "hide"); } } } + if (unhidden) { for (var i$1 = 0; i$1 < unhidden.length; ++i$1) + { if (unhidden[i$1].lines.length) { signal(unhidden[i$1], "unhide"); } } } + + if (display.wrapper.offsetHeight) + { doc.scrollTop = cm.display.scroller.scrollTop; } + + // Fire change events, and delayed event handlers + if (op.changeObjs) + { signal(cm, "changes", cm, op.changeObjs); } + if (op.update) + { op.update.finish(); } + } + + // Run the given function in an operation + function runInOp(cm, f) { + if (cm.curOp) { return f() } + startOperation(cm); + try { return f() } + finally { endOperation(cm); } + } + // Wraps a function in an operation. Returns the wrapped function. + function operation(cm, f) { + return function() { + if (cm.curOp) { return f.apply(cm, arguments) } + startOperation(cm); + try { return f.apply(cm, arguments) } + finally { endOperation(cm); } + } + } + // Used to add methods to editor and doc instances, wrapping them in + // operations. + function methodOp(f) { + return function() { + if (this.curOp) { return f.apply(this, arguments) } + startOperation(this); + try { return f.apply(this, arguments) } + finally { endOperation(this); } + } + } + function docMethodOp(f) { + return function() { + var cm = this.cm; + if (!cm || cm.curOp) { return f.apply(this, arguments) } + startOperation(cm); + try { return f.apply(this, arguments) } + finally { endOperation(cm); } + } + } + + // Updates the display.view data structure for a given change to the + // document. From and to are in pre-change coordinates. Lendiff is + // the amount of lines added or subtracted by the change. This is + // used for changes that span multiple lines, or change the way + // lines are divided into visual lines. regLineChange (below) + // registers single-line changes. + function regChange(cm, from, to, lendiff) { + if (from == null) { from = cm.doc.first; } + if (to == null) { to = cm.doc.first + cm.doc.size; } + if (!lendiff) { lendiff = 0; } + + var display = cm.display; + if (lendiff && to < display.viewTo && + (display.updateLineNumbers == null || display.updateLineNumbers > from)) + { display.updateLineNumbers = from; } + + cm.curOp.viewChanged = true; + + if (from >= display.viewTo) { // Change after + if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo) + { resetView(cm); } + } else if (to <= display.viewFrom) { // Change before + if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) { + resetView(cm); + } else { + display.viewFrom += lendiff; + display.viewTo += lendiff; + } + } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap + resetView(cm); + } else if (from <= display.viewFrom) { // Top overlap + var cut = viewCuttingPoint(cm, to, to + lendiff, 1); + if (cut) { + display.view = display.view.slice(cut.index); + display.viewFrom = cut.lineN; + display.viewTo += lendiff; + } else { + resetView(cm); + } + } else if (to >= display.viewTo) { // Bottom overlap + var cut$1 = viewCuttingPoint(cm, from, from, -1); + if (cut$1) { + display.view = display.view.slice(0, cut$1.index); + display.viewTo = cut$1.lineN; + } else { + resetView(cm); + } + } else { // Gap in the middle + var cutTop = viewCuttingPoint(cm, from, from, -1); + var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1); + if (cutTop && cutBot) { + display.view = display.view.slice(0, cutTop.index) + .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN)) + .concat(display.view.slice(cutBot.index)); + display.viewTo += lendiff; + } else { + resetView(cm); + } + } + + var ext = display.externalMeasured; + if (ext) { + if (to < ext.lineN) + { ext.lineN += lendiff; } + else if (from < ext.lineN + ext.size) + { display.externalMeasured = null; } + } + } + + // Register a change to a single line. Type must be one of "text", + // "gutter", "class", "widget" + function regLineChange(cm, line, type) { + cm.curOp.viewChanged = true; + var display = cm.display, ext = cm.display.externalMeasured; + if (ext && line >= ext.lineN && line < ext.lineN + ext.size) + { display.externalMeasured = null; } + + if (line < display.viewFrom || line >= display.viewTo) { return } + var lineView = display.view[findViewIndex(cm, line)]; + if (lineView.node == null) { return } + var arr = lineView.changes || (lineView.changes = []); + if (indexOf(arr, type) == -1) { arr.push(type); } + } + + // Clear the view. + function resetView(cm) { + cm.display.viewFrom = cm.display.viewTo = cm.doc.first; + cm.display.view = []; + cm.display.viewOffset = 0; + } + + function viewCuttingPoint(cm, oldN, newN, dir) { + var index = findViewIndex(cm, oldN), diff, view = cm.display.view; + if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size) + { return {index: index, lineN: newN} } + var n = cm.display.viewFrom; + for (var i = 0; i < index; i++) + { n += view[i].size; } + if (n != oldN) { + if (dir > 0) { + if (index == view.length - 1) { return null } + diff = (n + view[index].size) - oldN; + index++; + } else { + diff = n - oldN; + } + oldN += diff; newN += diff; + } + while (visualLineNo(cm.doc, newN) != newN) { + if (index == (dir < 0 ? 0 : view.length - 1)) { return null } + newN += dir * view[index - (dir < 0 ? 1 : 0)].size; + index += dir; + } + return {index: index, lineN: newN} + } + + // Force the view to cover a given range, adding empty view element + // or clipping off existing ones as needed. + function adjustView(cm, from, to) { + var display = cm.display, view = display.view; + if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) { + display.view = buildViewArray(cm, from, to); + display.viewFrom = from; + } else { + if (display.viewFrom > from) + { display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view); } + else if (display.viewFrom < from) + { display.view = display.view.slice(findViewIndex(cm, from)); } + display.viewFrom = from; + if (display.viewTo < to) + { display.view = display.view.concat(buildViewArray(cm, display.viewTo, to)); } + else if (display.viewTo > to) + { display.view = display.view.slice(0, findViewIndex(cm, to)); } + } + display.viewTo = to; + } + + // Count the number of lines in the view whose DOM representation is + // out of date (or nonexistent). + function countDirtyView(cm) { + var view = cm.display.view, dirty = 0; + for (var i = 0; i < view.length; i++) { + var lineView = view[i]; + if (!lineView.hidden && (!lineView.node || lineView.changes)) { ++dirty; } + } + return dirty + } + + // HIGHLIGHT WORKER + + function startWorker(cm, time) { + if (cm.doc.highlightFrontier < cm.display.viewTo) + { cm.state.highlight.set(time, bind(highlightWorker, cm)); } + } + + function highlightWorker(cm) { + var doc = cm.doc; + if (doc.highlightFrontier >= cm.display.viewTo) { return } + var end = +new Date + cm.options.workTime; + var context = getContextBefore(cm, doc.highlightFrontier); + var changedLines = []; + + doc.iter(context.line, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function (line) { + if (context.line >= cm.display.viewFrom) { // Visible + var oldStyles = line.styles; + var resetState = line.text.length > cm.options.maxHighlightLength ? copyState(doc.mode, context.state) : null; + var highlighted = highlightLine(cm, line, context, true); + if (resetState) { context.state = resetState; } + line.styles = highlighted.styles; + var oldCls = line.styleClasses, newCls = highlighted.classes; + if (newCls) { line.styleClasses = newCls; } + else if (oldCls) { line.styleClasses = null; } + var ischange = !oldStyles || oldStyles.length != line.styles.length || + oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass); + for (var i = 0; !ischange && i < oldStyles.length; ++i) { ischange = oldStyles[i] != line.styles[i]; } + if (ischange) { changedLines.push(context.line); } + line.stateAfter = context.save(); + context.nextLine(); + } else { + if (line.text.length <= cm.options.maxHighlightLength) + { processLine(cm, line.text, context); } + line.stateAfter = context.line % 5 == 0 ? context.save() : null; + context.nextLine(); + } + if (+new Date > end) { + startWorker(cm, cm.options.workDelay); + return true + } + }); + doc.highlightFrontier = context.line; + doc.modeFrontier = Math.max(doc.modeFrontier, context.line); + if (changedLines.length) { runInOp(cm, function () { + for (var i = 0; i < changedLines.length; i++) + { regLineChange(cm, changedLines[i], "text"); } + }); } + } + + // DISPLAY DRAWING + + var DisplayUpdate = function(cm, viewport, force) { + var display = cm.display; + + this.viewport = viewport; + // Store some values that we'll need later (but don't want to force a relayout for) + this.visible = visibleLines(display, cm.doc, viewport); + this.editorIsHidden = !display.wrapper.offsetWidth; + this.wrapperHeight = display.wrapper.clientHeight; + this.wrapperWidth = display.wrapper.clientWidth; + this.oldDisplayWidth = displayWidth(cm); + this.force = force; + this.dims = getDimensions(cm); + this.events = []; + }; + + DisplayUpdate.prototype.signal = function (emitter, type) { + if (hasHandler(emitter, type)) + { this.events.push(arguments); } + }; + DisplayUpdate.prototype.finish = function () { + var this$1 = this; + + for (var i = 0; i < this.events.length; i++) + { signal.apply(null, this$1.events[i]); } + }; + + function maybeClipScrollbars(cm) { + var display = cm.display; + if (!display.scrollbarsClipped && display.scroller.offsetWidth) { + display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth; + display.heightForcer.style.height = scrollGap(cm) + "px"; + display.sizer.style.marginBottom = -display.nativeBarWidth + "px"; + display.sizer.style.borderRightWidth = scrollGap(cm) + "px"; + display.scrollbarsClipped = true; + } + } + + function selectionSnapshot(cm) { + if (cm.hasFocus()) { return null } + var active = activeElt(); + if (!active || !contains(cm.display.lineDiv, active)) { return null } + var result = {activeElt: active}; + if (window.getSelection) { + var sel = window.getSelection(); + if (sel.anchorNode && sel.extend && contains(cm.display.lineDiv, sel.anchorNode)) { + result.anchorNode = sel.anchorNode; + result.anchorOffset = sel.anchorOffset; + result.focusNode = sel.focusNode; + result.focusOffset = sel.focusOffset; + } + } + return result + } + + function restoreSelection(snapshot) { + if (!snapshot || !snapshot.activeElt || snapshot.activeElt == activeElt()) { return } + snapshot.activeElt.focus(); + if (snapshot.anchorNode && contains(document.body, snapshot.anchorNode) && contains(document.body, snapshot.focusNode)) { + var sel = window.getSelection(), range$$1 = document.createRange(); + range$$1.setEnd(snapshot.anchorNode, snapshot.anchorOffset); + range$$1.collapse(false); + sel.removeAllRanges(); + sel.addRange(range$$1); + sel.extend(snapshot.focusNode, snapshot.focusOffset); + } + } + + // Does the actual updating of the line display. Bails out + // (returning false) when there is nothing to be done and forced is + // false. + function updateDisplayIfNeeded(cm, update) { + var display = cm.display, doc = cm.doc; + + if (update.editorIsHidden) { + resetView(cm); + return false + } + + // Bail out if the visible area is already rendered and nothing changed. + if (!update.force && + update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo && + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) && + display.renderedView == display.view && countDirtyView(cm) == 0) + { return false } + + if (maybeUpdateLineNumberWidth(cm)) { + resetView(cm); + update.dims = getDimensions(cm); + } + + // Compute a suitable new viewport (from & to) + var end = doc.first + doc.size; + var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first); + var to = Math.min(end, update.visible.to + cm.options.viewportMargin); + if (display.viewFrom < from && from - display.viewFrom < 20) { from = Math.max(doc.first, display.viewFrom); } + if (display.viewTo > to && display.viewTo - to < 20) { to = Math.min(end, display.viewTo); } + if (sawCollapsedSpans) { + from = visualLineNo(cm.doc, from); + to = visualLineEndNo(cm.doc, to); + } + + var different = from != display.viewFrom || to != display.viewTo || + display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth; + adjustView(cm, from, to); + + display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom)); + // Position the mover div to align with the current scroll position + cm.display.mover.style.top = display.viewOffset + "px"; + + var toUpdate = countDirtyView(cm); + if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view && + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo)) + { return false } + + // For big changes, we hide the enclosing element during the + // update, since that speeds up the operations on most browsers. + var selSnapshot = selectionSnapshot(cm); + if (toUpdate > 4) { display.lineDiv.style.display = "none"; } + patchDisplay(cm, display.updateLineNumbers, update.dims); + if (toUpdate > 4) { display.lineDiv.style.display = ""; } + display.renderedView = display.view; + // There might have been a widget with a focused element that got + // hidden or updated, if so re-focus it. + restoreSelection(selSnapshot); + + // Prevent selection and cursors from interfering with the scroll + // width and height. + removeChildren(display.cursorDiv); + removeChildren(display.selectionDiv); + display.gutters.style.height = display.sizer.style.minHeight = 0; + + if (different) { + display.lastWrapHeight = update.wrapperHeight; + display.lastWrapWidth = update.wrapperWidth; + startWorker(cm, 400); + } + + display.updateLineNumbers = null; + + return true + } + + function postUpdateDisplay(cm, update) { + var viewport = update.viewport; + + for (var first = true;; first = false) { + if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) { + // Clip forced viewport to actual scrollable area. + if (viewport && viewport.top != null) + { viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)}; } + // Updated line heights might result in the drawn area not + // actually covering the viewport. Keep looping until it does. + update.visible = visibleLines(cm.display, cm.doc, viewport); + if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo) + { break } + } + if (!updateDisplayIfNeeded(cm, update)) { break } + updateHeightsInViewport(cm); + var barMeasure = measureForScrollbars(cm); + updateSelection(cm); + updateScrollbars(cm, barMeasure); + setDocumentHeight(cm, barMeasure); + update.force = false; + } + + update.signal(cm, "update", cm); + if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) { + update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo); + cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo; + } + } + + function updateDisplaySimple(cm, viewport) { + var update = new DisplayUpdate(cm, viewport); + if (updateDisplayIfNeeded(cm, update)) { + updateHeightsInViewport(cm); + postUpdateDisplay(cm, update); + var barMeasure = measureForScrollbars(cm); + updateSelection(cm); + updateScrollbars(cm, barMeasure); + setDocumentHeight(cm, barMeasure); + update.finish(); + } + } + + // Sync the actual display DOM structure with display.view, removing + // nodes for lines that are no longer in view, and creating the ones + // that are not there yet, and updating the ones that are out of + // date. + function patchDisplay(cm, updateNumbersFrom, dims) { + var display = cm.display, lineNumbers = cm.options.lineNumbers; + var container = display.lineDiv, cur = container.firstChild; + + function rm(node) { + var next = node.nextSibling; + // Works around a throw-scroll bug in OS X Webkit + if (webkit && mac && cm.display.currentWheelTarget == node) + { node.style.display = "none"; } + else + { node.parentNode.removeChild(node); } + return next + } + + var view = display.view, lineN = display.viewFrom; + // Loop over the elements in the view, syncing cur (the DOM nodes + // in display.lineDiv) with the view as we go. + for (var i = 0; i < view.length; i++) { + var lineView = view[i]; + if (lineView.hidden) ; else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet + var node = buildLineElement(cm, lineView, lineN, dims); + container.insertBefore(node, cur); + } else { // Already drawn + while (cur != lineView.node) { cur = rm(cur); } + var updateNumber = lineNumbers && updateNumbersFrom != null && + updateNumbersFrom <= lineN && lineView.lineNumber; + if (lineView.changes) { + if (indexOf(lineView.changes, "gutter") > -1) { updateNumber = false; } + updateLineForChanges(cm, lineView, lineN, dims); + } + if (updateNumber) { + removeChildren(lineView.lineNumber); + lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN))); + } + cur = lineView.node.nextSibling; + } + lineN += lineView.size; + } + while (cur) { cur = rm(cur); } + } + + function updateGutterSpace(cm) { + var width = cm.display.gutters.offsetWidth; + cm.display.sizer.style.marginLeft = width + "px"; + } + + function setDocumentHeight(cm, measure) { + cm.display.sizer.style.minHeight = measure.docHeight + "px"; + cm.display.heightForcer.style.top = measure.docHeight + "px"; + cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + "px"; + } + + // Rebuild the gutter elements, ensure the margin to the left of the + // code matches their width. + function updateGutters(cm) { + var gutters = cm.display.gutters, specs = cm.options.gutters; + removeChildren(gutters); + var i = 0; + for (; i < specs.length; ++i) { + var gutterClass = specs[i]; + var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass)); + if (gutterClass == "CodeMirror-linenumbers") { + cm.display.lineGutter = gElt; + gElt.style.width = (cm.display.lineNumWidth || 1) + "px"; + } + } + gutters.style.display = i ? "" : "none"; + updateGutterSpace(cm); + } + + // Make sure the gutters options contains the element + // "CodeMirror-linenumbers" when the lineNumbers option is true. + function setGuttersForLineNumbers(options) { + var found = indexOf(options.gutters, "CodeMirror-linenumbers"); + if (found == -1 && options.lineNumbers) { + options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]); + } else if (found > -1 && !options.lineNumbers) { + options.gutters = options.gutters.slice(0); + options.gutters.splice(found, 1); + } + } + + // Since the delta values reported on mouse wheel events are + // unstandardized between browsers and even browser versions, and + // generally horribly unpredictable, this code starts by measuring + // the scroll effect that the first few mouse wheel events have, + // and, from that, detects the way it can convert deltas to pixel + // offsets afterwards. + // + // The reason we want to know the amount a wheel event will scroll + // is that it gives us a chance to update the display before the + // actual scrolling happens, reducing flickering. + + var wheelSamples = 0, wheelPixelsPerUnit = null; + // Fill in a browser-detected starting value on browsers where we + // know one. These don't have to be accurate -- the result of them + // being wrong would just be a slight flicker on the first wheel + // scroll (if it is large enough). + if (ie) { wheelPixelsPerUnit = -.53; } + else if (gecko) { wheelPixelsPerUnit = 15; } + else if (chrome) { wheelPixelsPerUnit = -.7; } + else if (safari) { wheelPixelsPerUnit = -1/3; } + + function wheelEventDelta(e) { + var dx = e.wheelDeltaX, dy = e.wheelDeltaY; + if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) { dx = e.detail; } + if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) { dy = e.detail; } + else if (dy == null) { dy = e.wheelDelta; } + return {x: dx, y: dy} + } + function wheelEventPixels(e) { + var delta = wheelEventDelta(e); + delta.x *= wheelPixelsPerUnit; + delta.y *= wheelPixelsPerUnit; + return delta + } + + function onScrollWheel(cm, e) { + var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y; + + var display = cm.display, scroll = display.scroller; + // Quit if there's nothing to scroll here + var canScrollX = scroll.scrollWidth > scroll.clientWidth; + var canScrollY = scroll.scrollHeight > scroll.clientHeight; + if (!(dx && canScrollX || dy && canScrollY)) { return } + + // Webkit browsers on OS X abort momentum scrolls when the target + // of the scroll event is removed from the scrollable element. + // This hack (see related code in patchDisplay) makes sure the + // element is kept around. + if (dy && mac && webkit) { + outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) { + for (var i = 0; i < view.length; i++) { + if (view[i].node == cur) { + cm.display.currentWheelTarget = cur; + break outer + } + } + } + } + + // On some browsers, horizontal scrolling will cause redraws to + // happen before the gutter has been realigned, causing it to + // wriggle around in a most unseemly way. When we have an + // estimated pixels/delta value, we just handle horizontal + // scrolling entirely here. It'll be slightly off from native, but + // better than glitching out. + if (dx && !gecko && !presto && wheelPixelsPerUnit != null) { + if (dy && canScrollY) + { updateScrollTop(cm, Math.max(0, scroll.scrollTop + dy * wheelPixelsPerUnit)); } + setScrollLeft(cm, Math.max(0, scroll.scrollLeft + dx * wheelPixelsPerUnit)); + // Only prevent default scrolling if vertical scrolling is + // actually possible. Otherwise, it causes vertical scroll + // jitter on OSX trackpads when deltaX is small and deltaY + // is large (issue #3579) + if (!dy || (dy && canScrollY)) + { e_preventDefault(e); } + display.wheelStartX = null; // Abort measurement, if in progress + return + } + + // 'Project' the visible viewport to cover the area that is being + // scrolled into view (if we know enough to estimate it). + if (dy && wheelPixelsPerUnit != null) { + var pixels = dy * wheelPixelsPerUnit; + var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight; + if (pixels < 0) { top = Math.max(0, top + pixels - 50); } + else { bot = Math.min(cm.doc.height, bot + pixels + 50); } + updateDisplaySimple(cm, {top: top, bottom: bot}); + } + + if (wheelSamples < 20) { + if (display.wheelStartX == null) { + display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop; + display.wheelDX = dx; display.wheelDY = dy; + setTimeout(function () { + if (display.wheelStartX == null) { return } + var movedX = scroll.scrollLeft - display.wheelStartX; + var movedY = scroll.scrollTop - display.wheelStartY; + var sample = (movedY && display.wheelDY && movedY / display.wheelDY) || + (movedX && display.wheelDX && movedX / display.wheelDX); + display.wheelStartX = display.wheelStartY = null; + if (!sample) { return } + wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1); + ++wheelSamples; + }, 200); + } else { + display.wheelDX += dx; display.wheelDY += dy; + } + } + } + + // Selection objects are immutable. A new one is created every time + // the selection changes. A selection is one or more non-overlapping + // (and non-touching) ranges, sorted, and an integer that indicates + // which one is the primary selection (the one that's scrolled into + // view, that getCursor returns, etc). + var Selection = function(ranges, primIndex) { + this.ranges = ranges; + this.primIndex = primIndex; + }; + + Selection.prototype.primary = function () { return this.ranges[this.primIndex] }; + + Selection.prototype.equals = function (other) { + var this$1 = this; + + if (other == this) { return true } + if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) { return false } + for (var i = 0; i < this.ranges.length; i++) { + var here = this$1.ranges[i], there = other.ranges[i]; + if (!equalCursorPos(here.anchor, there.anchor) || !equalCursorPos(here.head, there.head)) { return false } + } + return true + }; + + Selection.prototype.deepCopy = function () { + var this$1 = this; + + var out = []; + for (var i = 0; i < this.ranges.length; i++) + { out[i] = new Range(copyPos(this$1.ranges[i].anchor), copyPos(this$1.ranges[i].head)); } + return new Selection(out, this.primIndex) + }; + + Selection.prototype.somethingSelected = function () { + var this$1 = this; + + for (var i = 0; i < this.ranges.length; i++) + { if (!this$1.ranges[i].empty()) { return true } } + return false + }; + + Selection.prototype.contains = function (pos, end) { + var this$1 = this; + + if (!end) { end = pos; } + for (var i = 0; i < this.ranges.length; i++) { + var range = this$1.ranges[i]; + if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0) + { return i } + } + return -1 + }; + + var Range = function(anchor, head) { + this.anchor = anchor; this.head = head; + }; + + Range.prototype.from = function () { return minPos(this.anchor, this.head) }; + Range.prototype.to = function () { return maxPos(this.anchor, this.head) }; + Range.prototype.empty = function () { return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch }; + + // Take an unsorted, potentially overlapping set of ranges, and + // build a selection out of it. 'Consumes' ranges array (modifying + // it). + function normalizeSelection(cm, ranges, primIndex) { + var mayTouch = cm && cm.options.selectionsMayTouch; + var prim = ranges[primIndex]; + ranges.sort(function (a, b) { return cmp(a.from(), b.from()); }); + primIndex = indexOf(ranges, prim); + for (var i = 1; i < ranges.length; i++) { + var cur = ranges[i], prev = ranges[i - 1]; + var diff = cmp(prev.to(), cur.from()); + if (mayTouch && !cur.empty() ? diff > 0 : diff >= 0) { + var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to()); + var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head; + if (i <= primIndex) { --primIndex; } + ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to)); + } + } + return new Selection(ranges, primIndex) + } + + function simpleSelection(anchor, head) { + return new Selection([new Range(anchor, head || anchor)], 0) + } + + // Compute the position of the end of a change (its 'to' property + // refers to the pre-change end). + function changeEnd(change) { + if (!change.text) { return change.to } + return Pos(change.from.line + change.text.length - 1, + lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0)) + } + + // Adjust a position to refer to the post-change position of the + // same text, or the end of the change if the change covers it. + function adjustForChange(pos, change) { + if (cmp(pos, change.from) < 0) { return pos } + if (cmp(pos, change.to) <= 0) { return changeEnd(change) } + + var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch; + if (pos.line == change.to.line) { ch += changeEnd(change).ch - change.to.ch; } + return Pos(line, ch) + } + + function computeSelAfterChange(doc, change) { + var out = []; + for (var i = 0; i < doc.sel.ranges.length; i++) { + var range = doc.sel.ranges[i]; + out.push(new Range(adjustForChange(range.anchor, change), + adjustForChange(range.head, change))); + } + return normalizeSelection(doc.cm, out, doc.sel.primIndex) + } + + function offsetPos(pos, old, nw) { + if (pos.line == old.line) + { return Pos(nw.line, pos.ch - old.ch + nw.ch) } + else + { return Pos(nw.line + (pos.line - old.line), pos.ch) } + } + + // Used by replaceSelections to allow moving the selection to the + // start or around the replaced test. Hint may be "start" or "around". + function computeReplacedSel(doc, changes, hint) { + var out = []; + var oldPrev = Pos(doc.first, 0), newPrev = oldPrev; + for (var i = 0; i < changes.length; i++) { + var change = changes[i]; + var from = offsetPos(change.from, oldPrev, newPrev); + var to = offsetPos(changeEnd(change), oldPrev, newPrev); + oldPrev = change.to; + newPrev = to; + if (hint == "around") { + var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0; + out[i] = new Range(inv ? to : from, inv ? from : to); + } else { + out[i] = new Range(from, from); + } + } + return new Selection(out, doc.sel.primIndex) + } + + // Used to get the editor into a consistent state again when options change. + + function loadMode(cm) { + cm.doc.mode = getMode(cm.options, cm.doc.modeOption); + resetModeState(cm); + } + + function resetModeState(cm) { + cm.doc.iter(function (line) { + if (line.stateAfter) { line.stateAfter = null; } + if (line.styles) { line.styles = null; } + }); + cm.doc.modeFrontier = cm.doc.highlightFrontier = cm.doc.first; + startWorker(cm, 100); + cm.state.modeGen++; + if (cm.curOp) { regChange(cm); } + } + + // DOCUMENT DATA STRUCTURE + + // By default, updates that start and end at the beginning of a line + // are treated specially, in order to make the association of line + // widgets and marker elements with the text behave more intuitive. + function isWholeLineUpdate(doc, change) { + return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" && + (!doc.cm || doc.cm.options.wholeLineUpdateBefore) + } + + // Perform a change on the document data structure. + function updateDoc(doc, change, markedSpans, estimateHeight$$1) { + function spansFor(n) {return markedSpans ? markedSpans[n] : null} + function update(line, text, spans) { + updateLine(line, text, spans, estimateHeight$$1); + signalLater(line, "change", line, change); + } + function linesFor(start, end) { + var result = []; + for (var i = start; i < end; ++i) + { result.push(new Line(text[i], spansFor(i), estimateHeight$$1)); } + return result + } + + var from = change.from, to = change.to, text = change.text; + var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line); + var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line; + + // Adjust the line structure + if (change.full) { + doc.insert(0, linesFor(0, text.length)); + doc.remove(text.length, doc.size - text.length); + } else if (isWholeLineUpdate(doc, change)) { + // This is a whole-line replace. Treated specially to make + // sure line objects move the way they are supposed to. + var added = linesFor(0, text.length - 1); + update(lastLine, lastLine.text, lastSpans); + if (nlines) { doc.remove(from.line, nlines); } + if (added.length) { doc.insert(from.line, added); } + } else if (firstLine == lastLine) { + if (text.length == 1) { + update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans); + } else { + var added$1 = linesFor(1, text.length - 1); + added$1.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight$$1)); + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); + doc.insert(from.line + 1, added$1); + } + } else if (text.length == 1) { + update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0)); + doc.remove(from.line + 1, nlines); + } else { + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); + update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans); + var added$2 = linesFor(1, text.length - 1); + if (nlines > 1) { doc.remove(from.line + 1, nlines - 1); } + doc.insert(from.line + 1, added$2); + } + + signalLater(doc, "change", doc, change); + } + + // Call f for all linked documents. + function linkedDocs(doc, f, sharedHistOnly) { + function propagate(doc, skip, sharedHist) { + if (doc.linked) { for (var i = 0; i < doc.linked.length; ++i) { + var rel = doc.linked[i]; + if (rel.doc == skip) { continue } + var shared = sharedHist && rel.sharedHist; + if (sharedHistOnly && !shared) { continue } + f(rel.doc, shared); + propagate(rel.doc, doc, shared); + } } + } + propagate(doc, null, true); + } + + // Attach a document to an editor. + function attachDoc(cm, doc) { + if (doc.cm) { throw new Error("This document is already in use.") } + cm.doc = doc; + doc.cm = cm; + estimateLineHeights(cm); + loadMode(cm); + setDirectionClass(cm); + if (!cm.options.lineWrapping) { findMaxLine(cm); } + cm.options.mode = doc.modeOption; + regChange(cm); + } + + function setDirectionClass(cm) { + (cm.doc.direction == "rtl" ? addClass : rmClass)(cm.display.lineDiv, "CodeMirror-rtl"); + } + + function directionChanged(cm) { + runInOp(cm, function () { + setDirectionClass(cm); + regChange(cm); + }); + } + + function History(startGen) { + // Arrays of change events and selections. Doing something adds an + // event to done and clears undo. Undoing moves events from done + // to undone, redoing moves them in the other direction. + this.done = []; this.undone = []; + this.undoDepth = Infinity; + // Used to track when changes can be merged into a single undo + // event + this.lastModTime = this.lastSelTime = 0; + this.lastOp = this.lastSelOp = null; + this.lastOrigin = this.lastSelOrigin = null; + // Used by the isClean() method + this.generation = this.maxGeneration = startGen || 1; + } + + // Create a history change event from an updateDoc-style change + // object. + function historyChangeFromChange(doc, change) { + var histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)}; + attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); + linkedDocs(doc, function (doc) { return attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); }, true); + return histChange + } + + // Pop all selection events off the end of a history array. Stop at + // a change event. + function clearSelectionEvents(array) { + while (array.length) { + var last = lst(array); + if (last.ranges) { array.pop(); } + else { break } + } + } + + // Find the top change event in the history. Pop off selection + // events that are in the way. + function lastChangeEvent(hist, force) { + if (force) { + clearSelectionEvents(hist.done); + return lst(hist.done) + } else if (hist.done.length && !lst(hist.done).ranges) { + return lst(hist.done) + } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) { + hist.done.pop(); + return lst(hist.done) + } + } + + // Register a change in the history. Merges changes that are within + // a single operation, or are close together with an origin that + // allows merging (starting with "+") into a single event. + function addChangeToHistory(doc, change, selAfter, opId) { + var hist = doc.history; + hist.undone.length = 0; + var time = +new Date, cur; + var last; + + if ((hist.lastOp == opId || + hist.lastOrigin == change.origin && change.origin && + ((change.origin.charAt(0) == "+" && hist.lastModTime > time - (doc.cm ? doc.cm.options.historyEventDelay : 500)) || + change.origin.charAt(0) == "*")) && + (cur = lastChangeEvent(hist, hist.lastOp == opId))) { + // Merge this change into the last event + last = lst(cur.changes); + if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) { + // Optimized case for simple insertion -- don't want to add + // new changesets for every character typed + last.to = changeEnd(change); + } else { + // Add new sub-event + cur.changes.push(historyChangeFromChange(doc, change)); + } + } else { + // Can not be merged, start a new event. + var before = lst(hist.done); + if (!before || !before.ranges) + { pushSelectionToHistory(doc.sel, hist.done); } + cur = {changes: [historyChangeFromChange(doc, change)], + generation: hist.generation}; + hist.done.push(cur); + while (hist.done.length > hist.undoDepth) { + hist.done.shift(); + if (!hist.done[0].ranges) { hist.done.shift(); } + } + } + hist.done.push(selAfter); + hist.generation = ++hist.maxGeneration; + hist.lastModTime = hist.lastSelTime = time; + hist.lastOp = hist.lastSelOp = opId; + hist.lastOrigin = hist.lastSelOrigin = change.origin; + + if (!last) { signal(doc, "historyAdded"); } + } + + function selectionEventCanBeMerged(doc, origin, prev, sel) { + var ch = origin.charAt(0); + return ch == "*" || + ch == "+" && + prev.ranges.length == sel.ranges.length && + prev.somethingSelected() == sel.somethingSelected() && + new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500) + } + + // Called whenever the selection changes, sets the new selection as + // the pending selection in the history, and pushes the old pending + // selection into the 'done' array when it was significantly + // different (in number of selected ranges, emptiness, or time). + function addSelectionToHistory(doc, sel, opId, options) { + var hist = doc.history, origin = options && options.origin; + + // A new event is started when the previous origin does not match + // the current, or the origins don't allow matching. Origins + // starting with * are always merged, those starting with + are + // merged when similar and close together in time. + if (opId == hist.lastSelOp || + (origin && hist.lastSelOrigin == origin && + (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin || + selectionEventCanBeMerged(doc, origin, lst(hist.done), sel)))) + { hist.done[hist.done.length - 1] = sel; } + else + { pushSelectionToHistory(sel, hist.done); } + + hist.lastSelTime = +new Date; + hist.lastSelOrigin = origin; + hist.lastSelOp = opId; + if (options && options.clearRedo !== false) + { clearSelectionEvents(hist.undone); } + } + + function pushSelectionToHistory(sel, dest) { + var top = lst(dest); + if (!(top && top.ranges && top.equals(sel))) + { dest.push(sel); } + } + + // Used to store marked span information in the history. + function attachLocalSpans(doc, change, from, to) { + var existing = change["spans_" + doc.id], n = 0; + doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function (line) { + if (line.markedSpans) + { (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans; } + ++n; + }); + } + + // When un/re-doing restores text containing marked spans, those + // that have been explicitly cleared should not be restored. + function removeClearedSpans(spans) { + if (!spans) { return null } + var out; + for (var i = 0; i < spans.length; ++i) { + if (spans[i].marker.explicitlyCleared) { if (!out) { out = spans.slice(0, i); } } + else if (out) { out.push(spans[i]); } + } + return !out ? spans : out.length ? out : null + } + + // Retrieve and filter the old marked spans stored in a change event. + function getOldSpans(doc, change) { + var found = change["spans_" + doc.id]; + if (!found) { return null } + var nw = []; + for (var i = 0; i < change.text.length; ++i) + { nw.push(removeClearedSpans(found[i])); } + return nw + } + + // Used for un/re-doing changes from the history. Combines the + // result of computing the existing spans with the set of spans that + // existed in the history (so that deleting around a span and then + // undoing brings back the span). + function mergeOldSpans(doc, change) { + var old = getOldSpans(doc, change); + var stretched = stretchSpansOverChange(doc, change); + if (!old) { return stretched } + if (!stretched) { return old } + + for (var i = 0; i < old.length; ++i) { + var oldCur = old[i], stretchCur = stretched[i]; + if (oldCur && stretchCur) { + spans: for (var j = 0; j < stretchCur.length; ++j) { + var span = stretchCur[j]; + for (var k = 0; k < oldCur.length; ++k) + { if (oldCur[k].marker == span.marker) { continue spans } } + oldCur.push(span); + } + } else if (stretchCur) { + old[i] = stretchCur; + } + } + return old + } + + // Used both to provide a JSON-safe object in .getHistory, and, when + // detaching a document, to split the history in two + function copyHistoryArray(events, newGroup, instantiateSel) { + var copy = []; + for (var i = 0; i < events.length; ++i) { + var event = events[i]; + if (event.ranges) { + copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event); + continue + } + var changes = event.changes, newChanges = []; + copy.push({changes: newChanges}); + for (var j = 0; j < changes.length; ++j) { + var change = changes[j], m = (void 0); + newChanges.push({from: change.from, to: change.to, text: change.text}); + if (newGroup) { for (var prop in change) { if (m = prop.match(/^spans_(\d+)$/)) { + if (indexOf(newGroup, Number(m[1])) > -1) { + lst(newChanges)[prop] = change[prop]; + delete change[prop]; + } + } } } + } + } + return copy + } + + // The 'scroll' parameter given to many of these indicated whether + // the new cursor position should be scrolled into view after + // modifying the selection. + + // If shift is held or the extend flag is set, extends a range to + // include a given position (and optionally a second position). + // Otherwise, simply returns the range between the given positions. + // Used for cursor motion and such. + function extendRange(range, head, other, extend) { + if (extend) { + var anchor = range.anchor; + if (other) { + var posBefore = cmp(head, anchor) < 0; + if (posBefore != (cmp(other, anchor) < 0)) { + anchor = head; + head = other; + } else if (posBefore != (cmp(head, other) < 0)) { + head = other; + } + } + return new Range(anchor, head) + } else { + return new Range(other || head, head) + } + } + + // Extend the primary selection range, discard the rest. + function extendSelection(doc, head, other, options, extend) { + if (extend == null) { extend = doc.cm && (doc.cm.display.shift || doc.extend); } + setSelection(doc, new Selection([extendRange(doc.sel.primary(), head, other, extend)], 0), options); + } + + // Extend all selections (pos is an array of selections with length + // equal the number of selections) + function extendSelections(doc, heads, options) { + var out = []; + var extend = doc.cm && (doc.cm.display.shift || doc.extend); + for (var i = 0; i < doc.sel.ranges.length; i++) + { out[i] = extendRange(doc.sel.ranges[i], heads[i], null, extend); } + var newSel = normalizeSelection(doc.cm, out, doc.sel.primIndex); + setSelection(doc, newSel, options); + } + + // Updates a single range in the selection. + function replaceOneSelection(doc, i, range, options) { + var ranges = doc.sel.ranges.slice(0); + ranges[i] = range; + setSelection(doc, normalizeSelection(doc.cm, ranges, doc.sel.primIndex), options); + } + + // Reset the selection to a single range. + function setSimpleSelection(doc, anchor, head, options) { + setSelection(doc, simpleSelection(anchor, head), options); + } + + // Give beforeSelectionChange handlers a change to influence a + // selection update. + function filterSelectionChange(doc, sel, options) { + var obj = { + ranges: sel.ranges, + update: function(ranges) { + var this$1 = this; + + this.ranges = []; + for (var i = 0; i < ranges.length; i++) + { this$1.ranges[i] = new Range(clipPos(doc, ranges[i].anchor), + clipPos(doc, ranges[i].head)); } + }, + origin: options && options.origin + }; + signal(doc, "beforeSelectionChange", doc, obj); + if (doc.cm) { signal(doc.cm, "beforeSelectionChange", doc.cm, obj); } + if (obj.ranges != sel.ranges) { return normalizeSelection(doc.cm, obj.ranges, obj.ranges.length - 1) } + else { return sel } + } + + function setSelectionReplaceHistory(doc, sel, options) { + var done = doc.history.done, last = lst(done); + if (last && last.ranges) { + done[done.length - 1] = sel; + setSelectionNoUndo(doc, sel, options); + } else { + setSelection(doc, sel, options); + } + } + + // Set a new selection. + function setSelection(doc, sel, options) { + setSelectionNoUndo(doc, sel, options); + addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options); + } + + function setSelectionNoUndo(doc, sel, options) { + if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) + { sel = filterSelectionChange(doc, sel, options); } + + var bias = options && options.bias || + (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1); + setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true)); + + if (!(options && options.scroll === false) && doc.cm) + { ensureCursorVisible(doc.cm); } + } + + function setSelectionInner(doc, sel) { + if (sel.equals(doc.sel)) { return } + + doc.sel = sel; + + if (doc.cm) { + doc.cm.curOp.updateInput = 1; + doc.cm.curOp.selectionChanged = true; + signalCursorActivity(doc.cm); + } + signalLater(doc, "cursorActivity", doc); + } + + // Verify that the selection does not partially select any atomic + // marked ranges. + function reCheckSelection(doc) { + setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false)); + } + + // Return a selection that does not partially select any atomic + // ranges. + function skipAtomicInSelection(doc, sel, bias, mayClear) { + var out; + for (var i = 0; i < sel.ranges.length; i++) { + var range = sel.ranges[i]; + var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i]; + var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear); + var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear); + if (out || newAnchor != range.anchor || newHead != range.head) { + if (!out) { out = sel.ranges.slice(0, i); } + out[i] = new Range(newAnchor, newHead); + } + } + return out ? normalizeSelection(doc.cm, out, sel.primIndex) : sel + } + + function skipAtomicInner(doc, pos, oldPos, dir, mayClear) { + var line = getLine(doc, pos.line); + if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) { + var sp = line.markedSpans[i], m = sp.marker; + if ((sp.from == null || (m.inclusiveLeft ? sp.from <= pos.ch : sp.from < pos.ch)) && + (sp.to == null || (m.inclusiveRight ? sp.to >= pos.ch : sp.to > pos.ch))) { + if (mayClear) { + signal(m, "beforeCursorEnter"); + if (m.explicitlyCleared) { + if (!line.markedSpans) { break } + else {--i; continue} + } + } + if (!m.atomic) { continue } + + if (oldPos) { + var near = m.find(dir < 0 ? 1 : -1), diff = (void 0); + if (dir < 0 ? m.inclusiveRight : m.inclusiveLeft) + { near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null); } + if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0)) + { return skipAtomicInner(doc, near, pos, dir, mayClear) } + } + + var far = m.find(dir < 0 ? -1 : 1); + if (dir < 0 ? m.inclusiveLeft : m.inclusiveRight) + { far = movePos(doc, far, dir, far.line == pos.line ? line : null); } + return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null + } + } } + return pos + } + + // Ensure a given position is not inside an atomic range. + function skipAtomic(doc, pos, oldPos, bias, mayClear) { + var dir = bias || 1; + var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) || + (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) || + skipAtomicInner(doc, pos, oldPos, -dir, mayClear) || + (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true)); + if (!found) { + doc.cantEdit = true; + return Pos(doc.first, 0) + } + return found + } + + function movePos(doc, pos, dir, line) { + if (dir < 0 && pos.ch == 0) { + if (pos.line > doc.first) { return clipPos(doc, Pos(pos.line - 1)) } + else { return null } + } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) { + if (pos.line < doc.first + doc.size - 1) { return Pos(pos.line + 1, 0) } + else { return null } + } else { + return new Pos(pos.line, pos.ch + dir) + } + } + + function selectAll(cm) { + cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll); + } + + // UPDATING + + // Allow "beforeChange" event handlers to influence a change + function filterChange(doc, change, update) { + var obj = { + canceled: false, + from: change.from, + to: change.to, + text: change.text, + origin: change.origin, + cancel: function () { return obj.canceled = true; } + }; + if (update) { obj.update = function (from, to, text, origin) { + if (from) { obj.from = clipPos(doc, from); } + if (to) { obj.to = clipPos(doc, to); } + if (text) { obj.text = text; } + if (origin !== undefined) { obj.origin = origin; } + }; } + signal(doc, "beforeChange", doc, obj); + if (doc.cm) { signal(doc.cm, "beforeChange", doc.cm, obj); } + + if (obj.canceled) { + if (doc.cm) { doc.cm.curOp.updateInput = 2; } + return null + } + return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin} + } + + // Apply a change to a document, and add it to the document's + // history, and propagating it to all linked documents. + function makeChange(doc, change, ignoreReadOnly) { + if (doc.cm) { + if (!doc.cm.curOp) { return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly) } + if (doc.cm.state.suppressEdits) { return } + } + + if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) { + change = filterChange(doc, change, true); + if (!change) { return } + } + + // Possibly split or suppress the update based on the presence + // of read-only spans in its range. + var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to); + if (split) { + for (var i = split.length - 1; i >= 0; --i) + { makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text, origin: change.origin}); } + } else { + makeChangeInner(doc, change); + } + } + + function makeChangeInner(doc, change) { + if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) { return } + var selAfter = computeSelAfterChange(doc, change); + addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN); + + makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change)); + var rebased = []; + + linkedDocs(doc, function (doc, sharedHist) { + if (!sharedHist && indexOf(rebased, doc.history) == -1) { + rebaseHist(doc.history, change); + rebased.push(doc.history); + } + makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)); + }); + } + + // Revert a change stored in a document's history. + function makeChangeFromHistory(doc, type, allowSelectionOnly) { + var suppress = doc.cm && doc.cm.state.suppressEdits; + if (suppress && !allowSelectionOnly) { return } + + var hist = doc.history, event, selAfter = doc.sel; + var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done; + + // Verify that there is a useable event (so that ctrl-z won't + // needlessly clear selection events) + var i = 0; + for (; i < source.length; i++) { + event = source[i]; + if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges) + { break } + } + if (i == source.length) { return } + hist.lastOrigin = hist.lastSelOrigin = null; + + for (;;) { + event = source.pop(); + if (event.ranges) { + pushSelectionToHistory(event, dest); + if (allowSelectionOnly && !event.equals(doc.sel)) { + setSelection(doc, event, {clearRedo: false}); + return + } + selAfter = event; + } else if (suppress) { + source.push(event); + return + } else { break } + } + + // Build up a reverse change object to add to the opposite history + // stack (redo when undoing, and vice versa). + var antiChanges = []; + pushSelectionToHistory(selAfter, dest); + dest.push({changes: antiChanges, generation: hist.generation}); + hist.generation = event.generation || ++hist.maxGeneration; + + var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange"); + + var loop = function ( i ) { + var change = event.changes[i]; + change.origin = type; + if (filter && !filterChange(doc, change, false)) { + source.length = 0; + return {} + } + + antiChanges.push(historyChangeFromChange(doc, change)); + + var after = i ? computeSelAfterChange(doc, change) : lst(source); + makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)); + if (!i && doc.cm) { doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)}); } + var rebased = []; + + // Propagate to the linked documents + linkedDocs(doc, function (doc, sharedHist) { + if (!sharedHist && indexOf(rebased, doc.history) == -1) { + rebaseHist(doc.history, change); + rebased.push(doc.history); + } + makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change)); + }); + }; + + for (var i$1 = event.changes.length - 1; i$1 >= 0; --i$1) { + var returned = loop( i$1 ); + + if ( returned ) return returned.v; + } + } + + // Sub-views need their line numbers shifted when text is added + // above or below them in the parent document. + function shiftDoc(doc, distance) { + if (distance == 0) { return } + doc.first += distance; + doc.sel = new Selection(map(doc.sel.ranges, function (range) { return new Range( + Pos(range.anchor.line + distance, range.anchor.ch), + Pos(range.head.line + distance, range.head.ch) + ); }), doc.sel.primIndex); + if (doc.cm) { + regChange(doc.cm, doc.first, doc.first - distance, distance); + for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++) + { regLineChange(doc.cm, l, "gutter"); } + } + } + + // More lower-level change function, handling only a single document + // (not linked ones). + function makeChangeSingleDoc(doc, change, selAfter, spans) { + if (doc.cm && !doc.cm.curOp) + { return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans) } + + if (change.to.line < doc.first) { + shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line)); + return + } + if (change.from.line > doc.lastLine()) { return } + + // Clip the change to the size of this doc + if (change.from.line < doc.first) { + var shift = change.text.length - 1 - (doc.first - change.from.line); + shiftDoc(doc, shift); + change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch), + text: [lst(change.text)], origin: change.origin}; + } + var last = doc.lastLine(); + if (change.to.line > last) { + change = {from: change.from, to: Pos(last, getLine(doc, last).text.length), + text: [change.text[0]], origin: change.origin}; + } + + change.removed = getBetween(doc, change.from, change.to); + + if (!selAfter) { selAfter = computeSelAfterChange(doc, change); } + if (doc.cm) { makeChangeSingleDocInEditor(doc.cm, change, spans); } + else { updateDoc(doc, change, spans); } + setSelectionNoUndo(doc, selAfter, sel_dontScroll); + } + + // Handle the interaction of a change to a document with the editor + // that this document is part of. + function makeChangeSingleDocInEditor(cm, change, spans) { + var doc = cm.doc, display = cm.display, from = change.from, to = change.to; + + var recomputeMaxLength = false, checkWidthStart = from.line; + if (!cm.options.lineWrapping) { + checkWidthStart = lineNo(visualLine(getLine(doc, from.line))); + doc.iter(checkWidthStart, to.line + 1, function (line) { + if (line == display.maxLine) { + recomputeMaxLength = true; + return true + } + }); + } + + if (doc.sel.contains(change.from, change.to) > -1) + { signalCursorActivity(cm); } + + updateDoc(doc, change, spans, estimateHeight(cm)); + + if (!cm.options.lineWrapping) { + doc.iter(checkWidthStart, from.line + change.text.length, function (line) { + var len = lineLength(line); + if (len > display.maxLineLength) { + display.maxLine = line; + display.maxLineLength = len; + display.maxLineChanged = true; + recomputeMaxLength = false; + } + }); + if (recomputeMaxLength) { cm.curOp.updateMaxLine = true; } + } + + retreatFrontier(doc, from.line); + startWorker(cm, 400); + + var lendiff = change.text.length - (to.line - from.line) - 1; + // Remember that these lines changed, for updating the display + if (change.full) + { regChange(cm); } + else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change)) + { regLineChange(cm, from.line, "text"); } + else + { regChange(cm, from.line, to.line + 1, lendiff); } + + var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change"); + if (changeHandler || changesHandler) { + var obj = { + from: from, to: to, + text: change.text, + removed: change.removed, + origin: change.origin + }; + if (changeHandler) { signalLater(cm, "change", cm, obj); } + if (changesHandler) { (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj); } + } + cm.display.selForContextMenu = null; + } + + function replaceRange(doc, code, from, to, origin) { + var assign; + + if (!to) { to = from; } + if (cmp(to, from) < 0) { (assign = [to, from], from = assign[0], to = assign[1]); } + if (typeof code == "string") { code = doc.splitLines(code); } + makeChange(doc, {from: from, to: to, text: code, origin: origin}); + } + + // Rebasing/resetting history to deal with externally-sourced changes + + function rebaseHistSelSingle(pos, from, to, diff) { + if (to < pos.line) { + pos.line += diff; + } else if (from < pos.line) { + pos.line = from; + pos.ch = 0; + } + } + + // Tries to rebase an array of history events given a change in the + // document. If the change touches the same lines as the event, the + // event, and everything 'behind' it, is discarded. If the change is + // before the event, the event's positions are updated. Uses a + // copy-on-write scheme for the positions, to avoid having to + // reallocate them all on every rebase, but also avoid problems with + // shared position objects being unsafely updated. + function rebaseHistArray(array, from, to, diff) { + for (var i = 0; i < array.length; ++i) { + var sub = array[i], ok = true; + if (sub.ranges) { + if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true; } + for (var j = 0; j < sub.ranges.length; j++) { + rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff); + rebaseHistSelSingle(sub.ranges[j].head, from, to, diff); + } + continue + } + for (var j$1 = 0; j$1 < sub.changes.length; ++j$1) { + var cur = sub.changes[j$1]; + if (to < cur.from.line) { + cur.from = Pos(cur.from.line + diff, cur.from.ch); + cur.to = Pos(cur.to.line + diff, cur.to.ch); + } else if (from <= cur.to.line) { + ok = false; + break + } + } + if (!ok) { + array.splice(0, i + 1); + i = 0; + } + } + } + + function rebaseHist(hist, change) { + var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1; + rebaseHistArray(hist.done, from, to, diff); + rebaseHistArray(hist.undone, from, to, diff); + } + + // Utility for applying a change to a line by handle or number, + // returning the number and optionally registering the line as + // changed. + function changeLine(doc, handle, changeType, op) { + var no = handle, line = handle; + if (typeof handle == "number") { line = getLine(doc, clipLine(doc, handle)); } + else { no = lineNo(handle); } + if (no == null) { return null } + if (op(line, no) && doc.cm) { regLineChange(doc.cm, no, changeType); } + return line + } + + // The document is represented as a BTree consisting of leaves, with + // chunk of lines in them, and branches, with up to ten leaves or + // other branch nodes below them. The top node is always a branch + // node, and is the document object itself (meaning it has + // additional methods and properties). + // + // All nodes have parent links. The tree is used both to go from + // line numbers to line objects, and to go from objects to numbers. + // It also indexes by height, and is used to convert between height + // and line object, and to find the total height of the document. + // + // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html + + function LeafChunk(lines) { + var this$1 = this; + + this.lines = lines; + this.parent = null; + var height = 0; + for (var i = 0; i < lines.length; ++i) { + lines[i].parent = this$1; + height += lines[i].height; + } + this.height = height; + } + + LeafChunk.prototype = { + chunkSize: function() { return this.lines.length }, + + // Remove the n lines at offset 'at'. + removeInner: function(at, n) { + var this$1 = this; + + for (var i = at, e = at + n; i < e; ++i) { + var line = this$1.lines[i]; + this$1.height -= line.height; + cleanUpLine(line); + signalLater(line, "delete"); + } + this.lines.splice(at, n); + }, + + // Helper used to collapse a small branch into a single leaf. + collapse: function(lines) { + lines.push.apply(lines, this.lines); + }, + + // Insert the given array of lines at offset 'at', count them as + // having the given height. + insertInner: function(at, lines, height) { + var this$1 = this; + + this.height += height; + this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)); + for (var i = 0; i < lines.length; ++i) { lines[i].parent = this$1; } + }, + + // Used to iterate over a part of the tree. + iterN: function(at, n, op) { + var this$1 = this; + + for (var e = at + n; at < e; ++at) + { if (op(this$1.lines[at])) { return true } } + } + }; + + function BranchChunk(children) { + var this$1 = this; + + this.children = children; + var size = 0, height = 0; + for (var i = 0; i < children.length; ++i) { + var ch = children[i]; + size += ch.chunkSize(); height += ch.height; + ch.parent = this$1; + } + this.size = size; + this.height = height; + this.parent = null; + } + + BranchChunk.prototype = { + chunkSize: function() { return this.size }, + + removeInner: function(at, n) { + var this$1 = this; + + this.size -= n; + for (var i = 0; i < this.children.length; ++i) { + var child = this$1.children[i], sz = child.chunkSize(); + if (at < sz) { + var rm = Math.min(n, sz - at), oldHeight = child.height; + child.removeInner(at, rm); + this$1.height -= oldHeight - child.height; + if (sz == rm) { this$1.children.splice(i--, 1); child.parent = null; } + if ((n -= rm) == 0) { break } + at = 0; + } else { at -= sz; } + } + // If the result is smaller than 25 lines, ensure that it is a + // single leaf node. + if (this.size - n < 25 && + (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) { + var lines = []; + this.collapse(lines); + this.children = [new LeafChunk(lines)]; + this.children[0].parent = this; + } + }, + + collapse: function(lines) { + var this$1 = this; + + for (var i = 0; i < this.children.length; ++i) { this$1.children[i].collapse(lines); } + }, + + insertInner: function(at, lines, height) { + var this$1 = this; + + this.size += lines.length; + this.height += height; + for (var i = 0; i < this.children.length; ++i) { + var child = this$1.children[i], sz = child.chunkSize(); + if (at <= sz) { + child.insertInner(at, lines, height); + if (child.lines && child.lines.length > 50) { + // To avoid memory thrashing when child.lines is huge (e.g. first view of a large file), it's never spliced. + // Instead, small slices are taken. They're taken in order because sequential memory accesses are fastest. + var remaining = child.lines.length % 25 + 25; + for (var pos = remaining; pos < child.lines.length;) { + var leaf = new LeafChunk(child.lines.slice(pos, pos += 25)); + child.height -= leaf.height; + this$1.children.splice(++i, 0, leaf); + leaf.parent = this$1; + } + child.lines = child.lines.slice(0, remaining); + this$1.maybeSpill(); + } + break + } + at -= sz; + } + }, + + // When a node has grown, check whether it should be split. + maybeSpill: function() { + if (this.children.length <= 10) { return } + var me = this; + do { + var spilled = me.children.splice(me.children.length - 5, 5); + var sibling = new BranchChunk(spilled); + if (!me.parent) { // Become the parent node + var copy = new BranchChunk(me.children); + copy.parent = me; + me.children = [copy, sibling]; + me = copy; + } else { + me.size -= sibling.size; + me.height -= sibling.height; + var myIndex = indexOf(me.parent.children, me); + me.parent.children.splice(myIndex + 1, 0, sibling); + } + sibling.parent = me.parent; + } while (me.children.length > 10) + me.parent.maybeSpill(); + }, + + iterN: function(at, n, op) { + var this$1 = this; + + for (var i = 0; i < this.children.length; ++i) { + var child = this$1.children[i], sz = child.chunkSize(); + if (at < sz) { + var used = Math.min(n, sz - at); + if (child.iterN(at, used, op)) { return true } + if ((n -= used) == 0) { break } + at = 0; + } else { at -= sz; } + } + } + }; + + // Line widgets are block elements displayed above or below a line. + + var LineWidget = function(doc, node, options) { + var this$1 = this; + + if (options) { for (var opt in options) { if (options.hasOwnProperty(opt)) + { this$1[opt] = options[opt]; } } } + this.doc = doc; + this.node = node; + }; + + LineWidget.prototype.clear = function () { + var this$1 = this; + + var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line); + if (no == null || !ws) { return } + for (var i = 0; i < ws.length; ++i) { if (ws[i] == this$1) { ws.splice(i--, 1); } } + if (!ws.length) { line.widgets = null; } + var height = widgetHeight(this); + updateLineHeight(line, Math.max(0, line.height - height)); + if (cm) { + runInOp(cm, function () { + adjustScrollWhenAboveVisible(cm, line, -height); + regLineChange(cm, no, "widget"); + }); + signalLater(cm, "lineWidgetCleared", cm, this, no); + } + }; + + LineWidget.prototype.changed = function () { + var this$1 = this; + + var oldH = this.height, cm = this.doc.cm, line = this.line; + this.height = null; + var diff = widgetHeight(this) - oldH; + if (!diff) { return } + if (!lineIsHidden(this.doc, line)) { updateLineHeight(line, line.height + diff); } + if (cm) { + runInOp(cm, function () { + cm.curOp.forceUpdate = true; + adjustScrollWhenAboveVisible(cm, line, diff); + signalLater(cm, "lineWidgetChanged", cm, this$1, lineNo(line)); + }); + } + }; + eventMixin(LineWidget); + + function adjustScrollWhenAboveVisible(cm, line, diff) { + if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop)) + { addToScrollTop(cm, diff); } + } + + function addLineWidget(doc, handle, node, options) { + var widget = new LineWidget(doc, node, options); + var cm = doc.cm; + if (cm && widget.noHScroll) { cm.display.alignWidgets = true; } + changeLine(doc, handle, "widget", function (line) { + var widgets = line.widgets || (line.widgets = []); + if (widget.insertAt == null) { widgets.push(widget); } + else { widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget); } + widget.line = line; + if (cm && !lineIsHidden(doc, line)) { + var aboveVisible = heightAtLine(line) < doc.scrollTop; + updateLineHeight(line, line.height + widgetHeight(widget)); + if (aboveVisible) { addToScrollTop(cm, widget.height); } + cm.curOp.forceUpdate = true; + } + return true + }); + if (cm) { signalLater(cm, "lineWidgetAdded", cm, widget, typeof handle == "number" ? handle : lineNo(handle)); } + return widget + } + + // TEXTMARKERS + + // Created with markText and setBookmark methods. A TextMarker is a + // handle that can be used to clear or find a marked position in the + // document. Line objects hold arrays (markedSpans) containing + // {from, to, marker} object pointing to such marker objects, and + // indicating that such a marker is present on that line. Multiple + // lines may point to the same marker when it spans across lines. + // The spans will have null for their from/to properties when the + // marker continues beyond the start/end of the line. Markers have + // links back to the lines they currently touch. + + // Collapsed markers have unique ids, in order to be able to order + // them, which is needed for uniquely determining an outer marker + // when they overlap (they may nest, but not partially overlap). + var nextMarkerId = 0; + + var TextMarker = function(doc, type) { + this.lines = []; + this.type = type; + this.doc = doc; + this.id = ++nextMarkerId; + }; + + // Clear the marker. + TextMarker.prototype.clear = function () { + var this$1 = this; + + if (this.explicitlyCleared) { return } + var cm = this.doc.cm, withOp = cm && !cm.curOp; + if (withOp) { startOperation(cm); } + if (hasHandler(this, "clear")) { + var found = this.find(); + if (found) { signalLater(this, "clear", found.from, found.to); } + } + var min = null, max = null; + for (var i = 0; i < this.lines.length; ++i) { + var line = this$1.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this$1); + if (cm && !this$1.collapsed) { regLineChange(cm, lineNo(line), "text"); } + else if (cm) { + if (span.to != null) { max = lineNo(line); } + if (span.from != null) { min = lineNo(line); } + } + line.markedSpans = removeMarkedSpan(line.markedSpans, span); + if (span.from == null && this$1.collapsed && !lineIsHidden(this$1.doc, line) && cm) + { updateLineHeight(line, textHeight(cm.display)); } + } + if (cm && this.collapsed && !cm.options.lineWrapping) { for (var i$1 = 0; i$1 < this.lines.length; ++i$1) { + var visual = visualLine(this$1.lines[i$1]), len = lineLength(visual); + if (len > cm.display.maxLineLength) { + cm.display.maxLine = visual; + cm.display.maxLineLength = len; + cm.display.maxLineChanged = true; + } + } } + + if (min != null && cm && this.collapsed) { regChange(cm, min, max + 1); } + this.lines.length = 0; + this.explicitlyCleared = true; + if (this.atomic && this.doc.cantEdit) { + this.doc.cantEdit = false; + if (cm) { reCheckSelection(cm.doc); } + } + if (cm) { signalLater(cm, "markerCleared", cm, this, min, max); } + if (withOp) { endOperation(cm); } + if (this.parent) { this.parent.clear(); } + }; + + // Find the position of the marker in the document. Returns a {from, + // to} object by default. Side can be passed to get a specific side + // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the + // Pos objects returned contain a line object, rather than a line + // number (used to prevent looking up the same line twice). + TextMarker.prototype.find = function (side, lineObj) { + var this$1 = this; + + if (side == null && this.type == "bookmark") { side = 1; } + var from, to; + for (var i = 0; i < this.lines.length; ++i) { + var line = this$1.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this$1); + if (span.from != null) { + from = Pos(lineObj ? line : lineNo(line), span.from); + if (side == -1) { return from } + } + if (span.to != null) { + to = Pos(lineObj ? line : lineNo(line), span.to); + if (side == 1) { return to } + } + } + return from && {from: from, to: to} + }; + + // Signals that the marker's widget changed, and surrounding layout + // should be recomputed. + TextMarker.prototype.changed = function () { + var this$1 = this; + + var pos = this.find(-1, true), widget = this, cm = this.doc.cm; + if (!pos || !cm) { return } + runInOp(cm, function () { + var line = pos.line, lineN = lineNo(pos.line); + var view = findViewForLine(cm, lineN); + if (view) { + clearLineMeasurementCacheFor(view); + cm.curOp.selectionChanged = cm.curOp.forceUpdate = true; + } + cm.curOp.updateMaxLine = true; + if (!lineIsHidden(widget.doc, line) && widget.height != null) { + var oldHeight = widget.height; + widget.height = null; + var dHeight = widgetHeight(widget) - oldHeight; + if (dHeight) + { updateLineHeight(line, line.height + dHeight); } + } + signalLater(cm, "markerChanged", cm, this$1); + }); + }; + + TextMarker.prototype.attachLine = function (line) { + if (!this.lines.length && this.doc.cm) { + var op = this.doc.cm.curOp; + if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) + { (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this); } + } + this.lines.push(line); + }; + + TextMarker.prototype.detachLine = function (line) { + this.lines.splice(indexOf(this.lines, line), 1); + if (!this.lines.length && this.doc.cm) { + var op = this.doc.cm.curOp + ;(op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this); + } + }; + eventMixin(TextMarker); + + // Create a marker, wire it up to the right lines, and + function markText(doc, from, to, options, type) { + // Shared markers (across linked documents) are handled separately + // (markTextShared will call out to this again, once per + // document). + if (options && options.shared) { return markTextShared(doc, from, to, options, type) } + // Ensure we are in an operation. + if (doc.cm && !doc.cm.curOp) { return operation(doc.cm, markText)(doc, from, to, options, type) } + + var marker = new TextMarker(doc, type), diff = cmp(from, to); + if (options) { copyObj(options, marker, false); } + // Don't connect empty markers unless clearWhenEmpty is false + if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false) + { return marker } + if (marker.replacedWith) { + // Showing up as a widget implies collapsed (widget replaces text) + marker.collapsed = true; + marker.widgetNode = eltP("span", [marker.replacedWith], "CodeMirror-widget"); + if (!options.handleMouseEvents) { marker.widgetNode.setAttribute("cm-ignore-events", "true"); } + if (options.insertLeft) { marker.widgetNode.insertLeft = true; } + } + if (marker.collapsed) { + if (conflictingCollapsedRange(doc, from.line, from, to, marker) || + from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker)) + { throw new Error("Inserting collapsed marker partially overlapping an existing one") } + seeCollapsedSpans(); + } + + if (marker.addToHistory) + { addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN); } + + var curLine = from.line, cm = doc.cm, updateMaxLine; + doc.iter(curLine, to.line + 1, function (line) { + if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine) + { updateMaxLine = true; } + if (marker.collapsed && curLine != from.line) { updateLineHeight(line, 0); } + addMarkedSpan(line, new MarkedSpan(marker, + curLine == from.line ? from.ch : null, + curLine == to.line ? to.ch : null)); + ++curLine; + }); + // lineIsHidden depends on the presence of the spans, so needs a second pass + if (marker.collapsed) { doc.iter(from.line, to.line + 1, function (line) { + if (lineIsHidden(doc, line)) { updateLineHeight(line, 0); } + }); } + + if (marker.clearOnEnter) { on(marker, "beforeCursorEnter", function () { return marker.clear(); }); } + + if (marker.readOnly) { + seeReadOnlySpans(); + if (doc.history.done.length || doc.history.undone.length) + { doc.clearHistory(); } + } + if (marker.collapsed) { + marker.id = ++nextMarkerId; + marker.atomic = true; + } + if (cm) { + // Sync editor state + if (updateMaxLine) { cm.curOp.updateMaxLine = true; } + if (marker.collapsed) + { regChange(cm, from.line, to.line + 1); } + else if (marker.className || marker.startStyle || marker.endStyle || marker.css || + marker.attributes || marker.title) + { for (var i = from.line; i <= to.line; i++) { regLineChange(cm, i, "text"); } } + if (marker.atomic) { reCheckSelection(cm.doc); } + signalLater(cm, "markerAdded", cm, marker); + } + return marker + } + + // SHARED TEXTMARKERS + + // A shared marker spans multiple linked documents. It is + // implemented as a meta-marker-object controlling multiple normal + // markers. + var SharedTextMarker = function(markers, primary) { + var this$1 = this; + + this.markers = markers; + this.primary = primary; + for (var i = 0; i < markers.length; ++i) + { markers[i].parent = this$1; } + }; + + SharedTextMarker.prototype.clear = function () { + var this$1 = this; + + if (this.explicitlyCleared) { return } + this.explicitlyCleared = true; + for (var i = 0; i < this.markers.length; ++i) + { this$1.markers[i].clear(); } + signalLater(this, "clear"); + }; + + SharedTextMarker.prototype.find = function (side, lineObj) { + return this.primary.find(side, lineObj) + }; + eventMixin(SharedTextMarker); + + function markTextShared(doc, from, to, options, type) { + options = copyObj(options); + options.shared = false; + var markers = [markText(doc, from, to, options, type)], primary = markers[0]; + var widget = options.widgetNode; + linkedDocs(doc, function (doc) { + if (widget) { options.widgetNode = widget.cloneNode(true); } + markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type)); + for (var i = 0; i < doc.linked.length; ++i) + { if (doc.linked[i].isParent) { return } } + primary = lst(markers); + }); + return new SharedTextMarker(markers, primary) + } + + function findSharedMarkers(doc) { + return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), function (m) { return m.parent; }) + } + + function copySharedMarkers(doc, markers) { + for (var i = 0; i < markers.length; i++) { + var marker = markers[i], pos = marker.find(); + var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to); + if (cmp(mFrom, mTo)) { + var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type); + marker.markers.push(subMark); + subMark.parent = marker; + } + } + } + + function detachSharedMarkers(markers) { + var loop = function ( i ) { + var marker = markers[i], linked = [marker.primary.doc]; + linkedDocs(marker.primary.doc, function (d) { return linked.push(d); }); + for (var j = 0; j < marker.markers.length; j++) { + var subMarker = marker.markers[j]; + if (indexOf(linked, subMarker.doc) == -1) { + subMarker.parent = null; + marker.markers.splice(j--, 1); + } + } + }; + + for (var i = 0; i < markers.length; i++) loop( i ); + } + + var nextDocId = 0; + var Doc = function(text, mode, firstLine, lineSep, direction) { + if (!(this instanceof Doc)) { return new Doc(text, mode, firstLine, lineSep, direction) } + if (firstLine == null) { firstLine = 0; } + + BranchChunk.call(this, [new LeafChunk([new Line("", null)])]); + this.first = firstLine; + this.scrollTop = this.scrollLeft = 0; + this.cantEdit = false; + this.cleanGeneration = 1; + this.modeFrontier = this.highlightFrontier = firstLine; + var start = Pos(firstLine, 0); + this.sel = simpleSelection(start); + this.history = new History(null); + this.id = ++nextDocId; + this.modeOption = mode; + this.lineSep = lineSep; + this.direction = (direction == "rtl") ? "rtl" : "ltr"; + this.extend = false; + + if (typeof text == "string") { text = this.splitLines(text); } + updateDoc(this, {from: start, to: start, text: text}); + setSelection(this, simpleSelection(start), sel_dontScroll); + }; + + Doc.prototype = createObj(BranchChunk.prototype, { + constructor: Doc, + // Iterate over the document. Supports two forms -- with only one + // argument, it calls that for each line in the document. With + // three, it iterates over the range given by the first two (with + // the second being non-inclusive). + iter: function(from, to, op) { + if (op) { this.iterN(from - this.first, to - from, op); } + else { this.iterN(this.first, this.first + this.size, from); } + }, + + // Non-public interface for adding and removing lines. + insert: function(at, lines) { + var height = 0; + for (var i = 0; i < lines.length; ++i) { height += lines[i].height; } + this.insertInner(at - this.first, lines, height); + }, + remove: function(at, n) { this.removeInner(at - this.first, n); }, + + // From here, the methods are part of the public interface. Most + // are also available from CodeMirror (editor) instances. + + getValue: function(lineSep) { + var lines = getLines(this, this.first, this.first + this.size); + if (lineSep === false) { return lines } + return lines.join(lineSep || this.lineSeparator()) + }, + setValue: docMethodOp(function(code) { + var top = Pos(this.first, 0), last = this.first + this.size - 1; + makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length), + text: this.splitLines(code), origin: "setValue", full: true}, true); + if (this.cm) { scrollToCoords(this.cm, 0, 0); } + setSelection(this, simpleSelection(top), sel_dontScroll); + }), + replaceRange: function(code, from, to, origin) { + from = clipPos(this, from); + to = to ? clipPos(this, to) : from; + replaceRange(this, code, from, to, origin); + }, + getRange: function(from, to, lineSep) { + var lines = getBetween(this, clipPos(this, from), clipPos(this, to)); + if (lineSep === false) { return lines } + return lines.join(lineSep || this.lineSeparator()) + }, + + getLine: function(line) {var l = this.getLineHandle(line); return l && l.text}, + + getLineHandle: function(line) {if (isLine(this, line)) { return getLine(this, line) }}, + getLineNumber: function(line) {return lineNo(line)}, + + getLineHandleVisualStart: function(line) { + if (typeof line == "number") { line = getLine(this, line); } + return visualLine(line) + }, + + lineCount: function() {return this.size}, + firstLine: function() {return this.first}, + lastLine: function() {return this.first + this.size - 1}, + + clipPos: function(pos) {return clipPos(this, pos)}, + + getCursor: function(start) { + var range$$1 = this.sel.primary(), pos; + if (start == null || start == "head") { pos = range$$1.head; } + else if (start == "anchor") { pos = range$$1.anchor; } + else if (start == "end" || start == "to" || start === false) { pos = range$$1.to(); } + else { pos = range$$1.from(); } + return pos + }, + listSelections: function() { return this.sel.ranges }, + somethingSelected: function() {return this.sel.somethingSelected()}, + + setCursor: docMethodOp(function(line, ch, options) { + setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options); + }), + setSelection: docMethodOp(function(anchor, head, options) { + setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options); + }), + extendSelection: docMethodOp(function(head, other, options) { + extendSelection(this, clipPos(this, head), other && clipPos(this, other), options); + }), + extendSelections: docMethodOp(function(heads, options) { + extendSelections(this, clipPosArray(this, heads), options); + }), + extendSelectionsBy: docMethodOp(function(f, options) { + var heads = map(this.sel.ranges, f); + extendSelections(this, clipPosArray(this, heads), options); + }), + setSelections: docMethodOp(function(ranges, primary, options) { + var this$1 = this; + + if (!ranges.length) { return } + var out = []; + for (var i = 0; i < ranges.length; i++) + { out[i] = new Range(clipPos(this$1, ranges[i].anchor), + clipPos(this$1, ranges[i].head)); } + if (primary == null) { primary = Math.min(ranges.length - 1, this.sel.primIndex); } + setSelection(this, normalizeSelection(this.cm, out, primary), options); + }), + addSelection: docMethodOp(function(anchor, head, options) { + var ranges = this.sel.ranges.slice(0); + ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor))); + setSelection(this, normalizeSelection(this.cm, ranges, ranges.length - 1), options); + }), + + getSelection: function(lineSep) { + var this$1 = this; + + var ranges = this.sel.ranges, lines; + for (var i = 0; i < ranges.length; i++) { + var sel = getBetween(this$1, ranges[i].from(), ranges[i].to()); + lines = lines ? lines.concat(sel) : sel; + } + if (lineSep === false) { return lines } + else { return lines.join(lineSep || this.lineSeparator()) } + }, + getSelections: function(lineSep) { + var this$1 = this; + + var parts = [], ranges = this.sel.ranges; + for (var i = 0; i < ranges.length; i++) { + var sel = getBetween(this$1, ranges[i].from(), ranges[i].to()); + if (lineSep !== false) { sel = sel.join(lineSep || this$1.lineSeparator()); } + parts[i] = sel; + } + return parts + }, + replaceSelection: function(code, collapse, origin) { + var dup = []; + for (var i = 0; i < this.sel.ranges.length; i++) + { dup[i] = code; } + this.replaceSelections(dup, collapse, origin || "+input"); + }, + replaceSelections: docMethodOp(function(code, collapse, origin) { + var this$1 = this; + + var changes = [], sel = this.sel; + for (var i = 0; i < sel.ranges.length; i++) { + var range$$1 = sel.ranges[i]; + changes[i] = {from: range$$1.from(), to: range$$1.to(), text: this$1.splitLines(code[i]), origin: origin}; + } + var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse); + for (var i$1 = changes.length - 1; i$1 >= 0; i$1--) + { makeChange(this$1, changes[i$1]); } + if (newSel) { setSelectionReplaceHistory(this, newSel); } + else if (this.cm) { ensureCursorVisible(this.cm); } + }), + undo: docMethodOp(function() {makeChangeFromHistory(this, "undo");}), + redo: docMethodOp(function() {makeChangeFromHistory(this, "redo");}), + undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true);}), + redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true);}), + + setExtending: function(val) {this.extend = val;}, + getExtending: function() {return this.extend}, + + historySize: function() { + var hist = this.history, done = 0, undone = 0; + for (var i = 0; i < hist.done.length; i++) { if (!hist.done[i].ranges) { ++done; } } + for (var i$1 = 0; i$1 < hist.undone.length; i$1++) { if (!hist.undone[i$1].ranges) { ++undone; } } + return {undo: done, redo: undone} + }, + clearHistory: function() {this.history = new History(this.history.maxGeneration);}, + + markClean: function() { + this.cleanGeneration = this.changeGeneration(true); + }, + changeGeneration: function(forceSplit) { + if (forceSplit) + { this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null; } + return this.history.generation + }, + isClean: function (gen) { + return this.history.generation == (gen || this.cleanGeneration) + }, + + getHistory: function() { + return {done: copyHistoryArray(this.history.done), + undone: copyHistoryArray(this.history.undone)} + }, + setHistory: function(histData) { + var hist = this.history = new History(this.history.maxGeneration); + hist.done = copyHistoryArray(histData.done.slice(0), null, true); + hist.undone = copyHistoryArray(histData.undone.slice(0), null, true); + }, + + setGutterMarker: docMethodOp(function(line, gutterID, value) { + return changeLine(this, line, "gutter", function (line) { + var markers = line.gutterMarkers || (line.gutterMarkers = {}); + markers[gutterID] = value; + if (!value && isEmpty(markers)) { line.gutterMarkers = null; } + return true + }) + }), + + clearGutter: docMethodOp(function(gutterID) { + var this$1 = this; + + this.iter(function (line) { + if (line.gutterMarkers && line.gutterMarkers[gutterID]) { + changeLine(this$1, line, "gutter", function () { + line.gutterMarkers[gutterID] = null; + if (isEmpty(line.gutterMarkers)) { line.gutterMarkers = null; } + return true + }); + } + }); + }), + + lineInfo: function(line) { + var n; + if (typeof line == "number") { + if (!isLine(this, line)) { return null } + n = line; + line = getLine(this, line); + if (!line) { return null } + } else { + n = lineNo(line); + if (n == null) { return null } + } + return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers, + textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass, + widgets: line.widgets} + }, + + addLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function (line) { + var prop = where == "text" ? "textClass" + : where == "background" ? "bgClass" + : where == "gutter" ? "gutterClass" : "wrapClass"; + if (!line[prop]) { line[prop] = cls; } + else if (classTest(cls).test(line[prop])) { return false } + else { line[prop] += " " + cls; } + return true + }) + }), + removeLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function (line) { + var prop = where == "text" ? "textClass" + : where == "background" ? "bgClass" + : where == "gutter" ? "gutterClass" : "wrapClass"; + var cur = line[prop]; + if (!cur) { return false } + else if (cls == null) { line[prop] = null; } + else { + var found = cur.match(classTest(cls)); + if (!found) { return false } + var end = found.index + found[0].length; + line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null; + } + return true + }) + }), + + addLineWidget: docMethodOp(function(handle, node, options) { + return addLineWidget(this, handle, node, options) + }), + removeLineWidget: function(widget) { widget.clear(); }, + + markText: function(from, to, options) { + return markText(this, clipPos(this, from), clipPos(this, to), options, options && options.type || "range") + }, + setBookmark: function(pos, options) { + var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), + insertLeft: options && options.insertLeft, + clearWhenEmpty: false, shared: options && options.shared, + handleMouseEvents: options && options.handleMouseEvents}; + pos = clipPos(this, pos); + return markText(this, pos, pos, realOpts, "bookmark") + }, + findMarksAt: function(pos) { + pos = clipPos(this, pos); + var markers = [], spans = getLine(this, pos.line).markedSpans; + if (spans) { for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if ((span.from == null || span.from <= pos.ch) && + (span.to == null || span.to >= pos.ch)) + { markers.push(span.marker.parent || span.marker); } + } } + return markers + }, + findMarks: function(from, to, filter) { + from = clipPos(this, from); to = clipPos(this, to); + var found = [], lineNo$$1 = from.line; + this.iter(from.line, to.line + 1, function (line) { + var spans = line.markedSpans; + if (spans) { for (var i = 0; i < spans.length; i++) { + var span = spans[i]; + if (!(span.to != null && lineNo$$1 == from.line && from.ch >= span.to || + span.from == null && lineNo$$1 != from.line || + span.from != null && lineNo$$1 == to.line && span.from >= to.ch) && + (!filter || filter(span.marker))) + { found.push(span.marker.parent || span.marker); } + } } + ++lineNo$$1; + }); + return found + }, + getAllMarks: function() { + var markers = []; + this.iter(function (line) { + var sps = line.markedSpans; + if (sps) { for (var i = 0; i < sps.length; ++i) + { if (sps[i].from != null) { markers.push(sps[i].marker); } } } + }); + return markers + }, + + posFromIndex: function(off) { + var ch, lineNo$$1 = this.first, sepSize = this.lineSeparator().length; + this.iter(function (line) { + var sz = line.text.length + sepSize; + if (sz > off) { ch = off; return true } + off -= sz; + ++lineNo$$1; + }); + return clipPos(this, Pos(lineNo$$1, ch)) + }, + indexFromPos: function (coords) { + coords = clipPos(this, coords); + var index = coords.ch; + if (coords.line < this.first || coords.ch < 0) { return 0 } + var sepSize = this.lineSeparator().length; + this.iter(this.first, coords.line, function (line) { // iter aborts when callback returns a truthy value + index += line.text.length + sepSize; + }); + return index + }, + + copy: function(copyHistory) { + var doc = new Doc(getLines(this, this.first, this.first + this.size), + this.modeOption, this.first, this.lineSep, this.direction); + doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft; + doc.sel = this.sel; + doc.extend = false; + if (copyHistory) { + doc.history.undoDepth = this.history.undoDepth; + doc.setHistory(this.getHistory()); + } + return doc + }, + + linkedDoc: function(options) { + if (!options) { options = {}; } + var from = this.first, to = this.first + this.size; + if (options.from != null && options.from > from) { from = options.from; } + if (options.to != null && options.to < to) { to = options.to; } + var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from, this.lineSep, this.direction); + if (options.sharedHist) { copy.history = this.history + ; }(this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist}); + copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}]; + copySharedMarkers(copy, findSharedMarkers(this)); + return copy + }, + unlinkDoc: function(other) { + var this$1 = this; + + if (other instanceof CodeMirror) { other = other.doc; } + if (this.linked) { for (var i = 0; i < this.linked.length; ++i) { + var link = this$1.linked[i]; + if (link.doc != other) { continue } + this$1.linked.splice(i, 1); + other.unlinkDoc(this$1); + detachSharedMarkers(findSharedMarkers(this$1)); + break + } } + // If the histories were shared, split them again + if (other.history == this.history) { + var splitIds = [other.id]; + linkedDocs(other, function (doc) { return splitIds.push(doc.id); }, true); + other.history = new History(null); + other.history.done = copyHistoryArray(this.history.done, splitIds); + other.history.undone = copyHistoryArray(this.history.undone, splitIds); + } + }, + iterLinkedDocs: function(f) {linkedDocs(this, f);}, + + getMode: function() {return this.mode}, + getEditor: function() {return this.cm}, + + splitLines: function(str) { + if (this.lineSep) { return str.split(this.lineSep) } + return splitLinesAuto(str) + }, + lineSeparator: function() { return this.lineSep || "\n" }, + + setDirection: docMethodOp(function (dir) { + if (dir != "rtl") { dir = "ltr"; } + if (dir == this.direction) { return } + this.direction = dir; + this.iter(function (line) { return line.order = null; }); + if (this.cm) { directionChanged(this.cm); } + }) + }); + + // Public alias. + Doc.prototype.eachLine = Doc.prototype.iter; + + // Kludge to work around strange IE behavior where it'll sometimes + // re-fire a series of drag-related events right after the drop (#1551) + var lastDrop = 0; + + function onDrop(e) { + var cm = this; + clearDragCursor(cm); + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) + { return } + e_preventDefault(e); + if (ie) { lastDrop = +new Date; } + var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files; + if (!pos || cm.isReadOnly()) { return } + // Might be a file drop, in which case we simply extract the text + // and insert it. + if (files && files.length && window.FileReader && window.File) { + var n = files.length, text = Array(n), read = 0; + var loadFile = function (file, i) { + if (cm.options.allowDropFileTypes && + indexOf(cm.options.allowDropFileTypes, file.type) == -1) + { return } + + var reader = new FileReader; + reader.onload = operation(cm, function () { + var content = reader.result; + if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) { content = ""; } + text[i] = content; + if (++read == n) { + pos = clipPos(cm.doc, pos); + var change = {from: pos, to: pos, + text: cm.doc.splitLines(text.join(cm.doc.lineSeparator())), + origin: "paste"}; + makeChange(cm.doc, change); + setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change))); + } + }); + reader.readAsText(file); + }; + for (var i = 0; i < n; ++i) { loadFile(files[i], i); } + } else { // Normal drop + // Don't do a replace if the drop happened inside of the selected text. + if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) { + cm.state.draggingText(e); + // Ensure the editor is re-focused + setTimeout(function () { return cm.display.input.focus(); }, 20); + return + } + try { + var text$1 = e.dataTransfer.getData("Text"); + if (text$1) { + var selected; + if (cm.state.draggingText && !cm.state.draggingText.copy) + { selected = cm.listSelections(); } + setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)); + if (selected) { for (var i$1 = 0; i$1 < selected.length; ++i$1) + { replaceRange(cm.doc, "", selected[i$1].anchor, selected[i$1].head, "drag"); } } + cm.replaceSelection(text$1, "around", "paste"); + cm.display.input.focus(); + } + } + catch(e){} + } + } + + function onDragStart(cm, e) { + if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return } + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) { return } + + e.dataTransfer.setData("Text", cm.getSelection()); + e.dataTransfer.effectAllowed = "copyMove"; + + // Use dummy image instead of default browsers image. + // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. + if (e.dataTransfer.setDragImage && !safari) { + var img = elt("img", null, null, "position: fixed; left: 0; top: 0;"); + img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; + if (presto) { + img.width = img.height = 1; + cm.display.wrapper.appendChild(img); + // Force a relayout, or Opera won't use our image for some obscure reason + img._top = img.offsetTop; + } + e.dataTransfer.setDragImage(img, 0, 0); + if (presto) { img.parentNode.removeChild(img); } + } + } + + function onDragOver(cm, e) { + var pos = posFromMouse(cm, e); + if (!pos) { return } + var frag = document.createDocumentFragment(); + drawSelectionCursor(cm, pos, frag); + if (!cm.display.dragCursor) { + cm.display.dragCursor = elt("div", null, "CodeMirror-cursors CodeMirror-dragcursors"); + cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursorDiv); + } + removeChildrenAndAdd(cm.display.dragCursor, frag); + } + + function clearDragCursor(cm) { + if (cm.display.dragCursor) { + cm.display.lineSpace.removeChild(cm.display.dragCursor); + cm.display.dragCursor = null; + } + } + + // These must be handled carefully, because naively registering a + // handler for each editor will cause the editors to never be + // garbage collected. + + function forEachCodeMirror(f) { + if (!document.getElementsByClassName) { return } + var byClass = document.getElementsByClassName("CodeMirror"), editors = []; + for (var i = 0; i < byClass.length; i++) { + var cm = byClass[i].CodeMirror; + if (cm) { editors.push(cm); } + } + if (editors.length) { editors[0].operation(function () { + for (var i = 0; i < editors.length; i++) { f(editors[i]); } + }); } + } + + var globalsRegistered = false; + function ensureGlobalHandlers() { + if (globalsRegistered) { return } + registerGlobalHandlers(); + globalsRegistered = true; + } + function registerGlobalHandlers() { + // When the window resizes, we need to refresh active editors. + var resizeTimer; + on(window, "resize", function () { + if (resizeTimer == null) { resizeTimer = setTimeout(function () { + resizeTimer = null; + forEachCodeMirror(onResize); + }, 100); } + }); + // When the window loses focus, we want to show the editor as blurred + on(window, "blur", function () { return forEachCodeMirror(onBlur); }); + } + // Called when the window resizes + function onResize(cm) { + var d = cm.display; + // Might be a text scaling operation, clear size caches. + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; + d.scrollbarsClipped = false; + cm.setSize(); + } + + var keyNames = { + 3: "Pause", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", + 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", + 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", + 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", + 106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 127: "Delete", 145: "ScrollLock", + 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", + 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete", + 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert" + }; + + // Number keys + for (var i = 0; i < 10; i++) { keyNames[i + 48] = keyNames[i + 96] = String(i); } + // Alphabetic keys + for (var i$1 = 65; i$1 <= 90; i$1++) { keyNames[i$1] = String.fromCharCode(i$1); } + // Function keys + for (var i$2 = 1; i$2 <= 12; i$2++) { keyNames[i$2 + 111] = keyNames[i$2 + 63235] = "F" + i$2; } + + var keyMap = {}; + + keyMap.basic = { + "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", + "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", + "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore", + "Tab": "defaultTab", "Shift-Tab": "indentAuto", + "Enter": "newlineAndIndent", "Insert": "toggleOverwrite", + "Esc": "singleSelection" + }; + // Note that the save and find-related commands aren't defined by + // default. User code or addons can define them. Unknown commands + // are simply ignored. + keyMap.pcDefault = { + "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", + "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown", + "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", + "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find", + "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", + "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", + "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection", + "fallthrough": "basic" + }; + // Very basic readline/emacs-style bindings, which are standard on Mac. + keyMap.emacsy = { + "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", + "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", + "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", + "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars", + "Ctrl-O": "openLine" + }; + keyMap.macDefault = { + "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", + "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", + "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore", + "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", + "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", + "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight", + "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd", + "fallthrough": ["basic", "emacsy"] + }; + keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; + + // KEYMAP DISPATCH + + function normalizeKeyName(name) { + var parts = name.split(/-(?!$)/); + name = parts[parts.length - 1]; + var alt, ctrl, shift, cmd; + for (var i = 0; i < parts.length - 1; i++) { + var mod = parts[i]; + if (/^(cmd|meta|m)$/i.test(mod)) { cmd = true; } + else if (/^a(lt)?$/i.test(mod)) { alt = true; } + else if (/^(c|ctrl|control)$/i.test(mod)) { ctrl = true; } + else if (/^s(hift)?$/i.test(mod)) { shift = true; } + else { throw new Error("Unrecognized modifier name: " + mod) } + } + if (alt) { name = "Alt-" + name; } + if (ctrl) { name = "Ctrl-" + name; } + if (cmd) { name = "Cmd-" + name; } + if (shift) { name = "Shift-" + name; } + return name + } + + // This is a kludge to keep keymaps mostly working as raw objects + // (backwards compatibility) while at the same time support features + // like normalization and multi-stroke key bindings. It compiles a + // new normalized keymap, and then updates the old object to reflect + // this. + function normalizeKeyMap(keymap) { + var copy = {}; + for (var keyname in keymap) { if (keymap.hasOwnProperty(keyname)) { + var value = keymap[keyname]; + if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) { continue } + if (value == "...") { delete keymap[keyname]; continue } + + var keys = map(keyname.split(" "), normalizeKeyName); + for (var i = 0; i < keys.length; i++) { + var val = (void 0), name = (void 0); + if (i == keys.length - 1) { + name = keys.join(" "); + val = value; + } else { + name = keys.slice(0, i + 1).join(" "); + val = "..."; + } + var prev = copy[name]; + if (!prev) { copy[name] = val; } + else if (prev != val) { throw new Error("Inconsistent bindings for " + name) } + } + delete keymap[keyname]; + } } + for (var prop in copy) { keymap[prop] = copy[prop]; } + return keymap + } + + function lookupKey(key, map$$1, handle, context) { + map$$1 = getKeyMap(map$$1); + var found = map$$1.call ? map$$1.call(key, context) : map$$1[key]; + if (found === false) { return "nothing" } + if (found === "...") { return "multi" } + if (found != null && handle(found)) { return "handled" } + + if (map$$1.fallthrough) { + if (Object.prototype.toString.call(map$$1.fallthrough) != "[object Array]") + { return lookupKey(key, map$$1.fallthrough, handle, context) } + for (var i = 0; i < map$$1.fallthrough.length; i++) { + var result = lookupKey(key, map$$1.fallthrough[i], handle, context); + if (result) { return result } + } + } + } + + // Modifier key presses don't count as 'real' key presses for the + // purpose of keymap fallthrough. + function isModifierKey(value) { + var name = typeof value == "string" ? value : keyNames[value.keyCode]; + return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod" + } + + function addModifierNames(name, event, noShift) { + var base = name; + if (event.altKey && base != "Alt") { name = "Alt-" + name; } + if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") { name = "Ctrl-" + name; } + if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") { name = "Cmd-" + name; } + if (!noShift && event.shiftKey && base != "Shift") { name = "Shift-" + name; } + return name + } + + // Look up the name of a key as indicated by an event object. + function keyName(event, noShift) { + if (presto && event.keyCode == 34 && event["char"]) { return false } + var name = keyNames[event.keyCode]; + if (name == null || event.altGraphKey) { return false } + // Ctrl-ScrollLock has keyCode 3, same as Ctrl-Pause, + // so we'll use event.code when available (Chrome 48+, FF 38+, Safari 10.1+) + if (event.keyCode == 3 && event.code) { name = event.code; } + return addModifierNames(name, event, noShift) + } + + function getKeyMap(val) { + return typeof val == "string" ? keyMap[val] : val + } + + // Helper for deleting text near the selection(s), used to implement + // backspace, delete, and similar functionality. + function deleteNearSelection(cm, compute) { + var ranges = cm.doc.sel.ranges, kill = []; + // Build up a set of ranges to kill first, merging overlapping + // ranges. + for (var i = 0; i < ranges.length; i++) { + var toKill = compute(ranges[i]); + while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) { + var replaced = kill.pop(); + if (cmp(replaced.from, toKill.from) < 0) { + toKill.from = replaced.from; + break + } + } + kill.push(toKill); + } + // Next, remove those actual ranges. + runInOp(cm, function () { + for (var i = kill.length - 1; i >= 0; i--) + { replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete"); } + ensureCursorVisible(cm); + }); + } + + function moveCharLogically(line, ch, dir) { + var target = skipExtendingChars(line.text, ch + dir, dir); + return target < 0 || target > line.text.length ? null : target + } + + function moveLogically(line, start, dir) { + var ch = moveCharLogically(line, start.ch, dir); + return ch == null ? null : new Pos(start.line, ch, dir < 0 ? "after" : "before") + } + + function endOfLine(visually, cm, lineObj, lineNo, dir) { + if (visually) { + var order = getOrder(lineObj, cm.doc.direction); + if (order) { + var part = dir < 0 ? lst(order) : order[0]; + var moveInStorageOrder = (dir < 0) == (part.level == 1); + var sticky = moveInStorageOrder ? "after" : "before"; + var ch; + // With a wrapped rtl chunk (possibly spanning multiple bidi parts), + // it could be that the last bidi part is not on the last visual line, + // since visual lines contain content order-consecutive chunks. + // Thus, in rtl, we are looking for the first (content-order) character + // in the rtl chunk that is on the last line (that is, the same line + // as the last (content-order) character). + if (part.level > 0 || cm.doc.direction == "rtl") { + var prep = prepareMeasureForLine(cm, lineObj); + ch = dir < 0 ? lineObj.text.length - 1 : 0; + var targetTop = measureCharPrepared(cm, prep, ch).top; + ch = findFirst(function (ch) { return measureCharPrepared(cm, prep, ch).top == targetTop; }, (dir < 0) == (part.level == 1) ? part.from : part.to - 1, ch); + if (sticky == "before") { ch = moveCharLogically(lineObj, ch, 1); } + } else { ch = dir < 0 ? part.to : part.from; } + return new Pos(lineNo, ch, sticky) + } + } + return new Pos(lineNo, dir < 0 ? lineObj.text.length : 0, dir < 0 ? "before" : "after") + } + + function moveVisually(cm, line, start, dir) { + var bidi = getOrder(line, cm.doc.direction); + if (!bidi) { return moveLogically(line, start, dir) } + if (start.ch >= line.text.length) { + start.ch = line.text.length; + start.sticky = "before"; + } else if (start.ch <= 0) { + start.ch = 0; + start.sticky = "after"; + } + var partPos = getBidiPartAt(bidi, start.ch, start.sticky), part = bidi[partPos]; + if (cm.doc.direction == "ltr" && part.level % 2 == 0 && (dir > 0 ? part.to > start.ch : part.from < start.ch)) { + // Case 1: We move within an ltr part in an ltr editor. Even with wrapped lines, + // nothing interesting happens. + return moveLogically(line, start, dir) + } + + var mv = function (pos, dir) { return moveCharLogically(line, pos instanceof Pos ? pos.ch : pos, dir); }; + var prep; + var getWrappedLineExtent = function (ch) { + if (!cm.options.lineWrapping) { return {begin: 0, end: line.text.length} } + prep = prep || prepareMeasureForLine(cm, line); + return wrappedLineExtentChar(cm, line, prep, ch) + }; + var wrappedLineExtent = getWrappedLineExtent(start.sticky == "before" ? mv(start, -1) : start.ch); + + if (cm.doc.direction == "rtl" || part.level == 1) { + var moveInStorageOrder = (part.level == 1) == (dir < 0); + var ch = mv(start, moveInStorageOrder ? 1 : -1); + if (ch != null && (!moveInStorageOrder ? ch >= part.from && ch >= wrappedLineExtent.begin : ch <= part.to && ch <= wrappedLineExtent.end)) { + // Case 2: We move within an rtl part or in an rtl editor on the same visual line + var sticky = moveInStorageOrder ? "before" : "after"; + return new Pos(start.line, ch, sticky) + } + } + + // Case 3: Could not move within this bidi part in this visual line, so leave + // the current bidi part + + var searchInVisualLine = function (partPos, dir, wrappedLineExtent) { + var getRes = function (ch, moveInStorageOrder) { return moveInStorageOrder + ? new Pos(start.line, mv(ch, 1), "before") + : new Pos(start.line, ch, "after"); }; + + for (; partPos >= 0 && partPos < bidi.length; partPos += dir) { + var part = bidi[partPos]; + var moveInStorageOrder = (dir > 0) == (part.level != 1); + var ch = moveInStorageOrder ? wrappedLineExtent.begin : mv(wrappedLineExtent.end, -1); + if (part.from <= ch && ch < part.to) { return getRes(ch, moveInStorageOrder) } + ch = moveInStorageOrder ? part.from : mv(part.to, -1); + if (wrappedLineExtent.begin <= ch && ch < wrappedLineExtent.end) { return getRes(ch, moveInStorageOrder) } + } + }; + + // Case 3a: Look for other bidi parts on the same visual line + var res = searchInVisualLine(partPos + dir, dir, wrappedLineExtent); + if (res) { return res } + + // Case 3b: Look for other bidi parts on the next visual line + var nextCh = dir > 0 ? wrappedLineExtent.end : mv(wrappedLineExtent.begin, -1); + if (nextCh != null && !(dir > 0 && nextCh == line.text.length)) { + res = searchInVisualLine(dir > 0 ? 0 : bidi.length - 1, dir, getWrappedLineExtent(nextCh)); + if (res) { return res } + } + + // Case 4: Nowhere to move + return null + } + + // Commands are parameter-less actions that can be performed on an + // editor, mostly used for keybindings. + var commands = { + selectAll: selectAll, + singleSelection: function (cm) { return cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll); }, + killLine: function (cm) { return deleteNearSelection(cm, function (range) { + if (range.empty()) { + var len = getLine(cm.doc, range.head.line).text.length; + if (range.head.ch == len && range.head.line < cm.lastLine()) + { return {from: range.head, to: Pos(range.head.line + 1, 0)} } + else + { return {from: range.head, to: Pos(range.head.line, len)} } + } else { + return {from: range.from(), to: range.to()} + } + }); }, + deleteLine: function (cm) { return deleteNearSelection(cm, function (range) { return ({ + from: Pos(range.from().line, 0), + to: clipPos(cm.doc, Pos(range.to().line + 1, 0)) + }); }); }, + delLineLeft: function (cm) { return deleteNearSelection(cm, function (range) { return ({ + from: Pos(range.from().line, 0), to: range.from() + }); }); }, + delWrappedLineLeft: function (cm) { return deleteNearSelection(cm, function (range) { + var top = cm.charCoords(range.head, "div").top + 5; + var leftPos = cm.coordsChar({left: 0, top: top}, "div"); + return {from: leftPos, to: range.from()} + }); }, + delWrappedLineRight: function (cm) { return deleteNearSelection(cm, function (range) { + var top = cm.charCoords(range.head, "div").top + 5; + var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"); + return {from: range.from(), to: rightPos } + }); }, + undo: function (cm) { return cm.undo(); }, + redo: function (cm) { return cm.redo(); }, + undoSelection: function (cm) { return cm.undoSelection(); }, + redoSelection: function (cm) { return cm.redoSelection(); }, + goDocStart: function (cm) { return cm.extendSelection(Pos(cm.firstLine(), 0)); }, + goDocEnd: function (cm) { return cm.extendSelection(Pos(cm.lastLine())); }, + goLineStart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStart(cm, range.head.line); }, + {origin: "+move", bias: 1} + ); }, + goLineStartSmart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStartSmart(cm, range.head); }, + {origin: "+move", bias: 1} + ); }, + goLineEnd: function (cm) { return cm.extendSelectionsBy(function (range) { return lineEnd(cm, range.head.line); }, + {origin: "+move", bias: -1} + ); }, + goLineRight: function (cm) { return cm.extendSelectionsBy(function (range) { + var top = cm.cursorCoords(range.head, "div").top + 5; + return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div") + }, sel_move); }, + goLineLeft: function (cm) { return cm.extendSelectionsBy(function (range) { + var top = cm.cursorCoords(range.head, "div").top + 5; + return cm.coordsChar({left: 0, top: top}, "div") + }, sel_move); }, + goLineLeftSmart: function (cm) { return cm.extendSelectionsBy(function (range) { + var top = cm.cursorCoords(range.head, "div").top + 5; + var pos = cm.coordsChar({left: 0, top: top}, "div"); + if (pos.ch < cm.getLine(pos.line).search(/\S/)) { return lineStartSmart(cm, range.head) } + return pos + }, sel_move); }, + goLineUp: function (cm) { return cm.moveV(-1, "line"); }, + goLineDown: function (cm) { return cm.moveV(1, "line"); }, + goPageUp: function (cm) { return cm.moveV(-1, "page"); }, + goPageDown: function (cm) { return cm.moveV(1, "page"); }, + goCharLeft: function (cm) { return cm.moveH(-1, "char"); }, + goCharRight: function (cm) { return cm.moveH(1, "char"); }, + goColumnLeft: function (cm) { return cm.moveH(-1, "column"); }, + goColumnRight: function (cm) { return cm.moveH(1, "column"); }, + goWordLeft: function (cm) { return cm.moveH(-1, "word"); }, + goGroupRight: function (cm) { return cm.moveH(1, "group"); }, + goGroupLeft: function (cm) { return cm.moveH(-1, "group"); }, + goWordRight: function (cm) { return cm.moveH(1, "word"); }, + delCharBefore: function (cm) { return cm.deleteH(-1, "char"); }, + delCharAfter: function (cm) { return cm.deleteH(1, "char"); }, + delWordBefore: function (cm) { return cm.deleteH(-1, "word"); }, + delWordAfter: function (cm) { return cm.deleteH(1, "word"); }, + delGroupBefore: function (cm) { return cm.deleteH(-1, "group"); }, + delGroupAfter: function (cm) { return cm.deleteH(1, "group"); }, + indentAuto: function (cm) { return cm.indentSelection("smart"); }, + indentMore: function (cm) { return cm.indentSelection("add"); }, + indentLess: function (cm) { return cm.indentSelection("subtract"); }, + insertTab: function (cm) { return cm.replaceSelection("\t"); }, + insertSoftTab: function (cm) { + var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize; + for (var i = 0; i < ranges.length; i++) { + var pos = ranges[i].from(); + var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize); + spaces.push(spaceStr(tabSize - col % tabSize)); + } + cm.replaceSelections(spaces); + }, + defaultTab: function (cm) { + if (cm.somethingSelected()) { cm.indentSelection("add"); } + else { cm.execCommand("insertTab"); } + }, + // Swap the two chars left and right of each selection's head. + // Move cursor behind the two swapped characters afterwards. + // + // Doesn't consider line feeds a character. + // Doesn't scan more than one line above to find a character. + // Doesn't do anything on an empty line. + // Doesn't do anything with non-empty selections. + transposeChars: function (cm) { return runInOp(cm, function () { + var ranges = cm.listSelections(), newSel = []; + for (var i = 0; i < ranges.length; i++) { + if (!ranges[i].empty()) { continue } + var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text; + if (line) { + if (cur.ch == line.length) { cur = new Pos(cur.line, cur.ch - 1); } + if (cur.ch > 0) { + cur = new Pos(cur.line, cur.ch + 1); + cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2), + Pos(cur.line, cur.ch - 2), cur, "+transpose"); + } else if (cur.line > cm.doc.first) { + var prev = getLine(cm.doc, cur.line - 1).text; + if (prev) { + cur = new Pos(cur.line, 1); + cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() + + prev.charAt(prev.length - 1), + Pos(cur.line - 1, prev.length - 1), cur, "+transpose"); + } + } + } + newSel.push(new Range(cur, cur)); + } + cm.setSelections(newSel); + }); }, + newlineAndIndent: function (cm) { return runInOp(cm, function () { + var sels = cm.listSelections(); + for (var i = sels.length - 1; i >= 0; i--) + { cm.replaceRange(cm.doc.lineSeparator(), sels[i].anchor, sels[i].head, "+input"); } + sels = cm.listSelections(); + for (var i$1 = 0; i$1 < sels.length; i$1++) + { cm.indentLine(sels[i$1].from().line, null, true); } + ensureCursorVisible(cm); + }); }, + openLine: function (cm) { return cm.replaceSelection("\n", "start"); }, + toggleOverwrite: function (cm) { return cm.toggleOverwrite(); } + }; + + + function lineStart(cm, lineN) { + var line = getLine(cm.doc, lineN); + var visual = visualLine(line); + if (visual != line) { lineN = lineNo(visual); } + return endOfLine(true, cm, visual, lineN, 1) + } + function lineEnd(cm, lineN) { + var line = getLine(cm.doc, lineN); + var visual = visualLineEnd(line); + if (visual != line) { lineN = lineNo(visual); } + return endOfLine(true, cm, line, lineN, -1) + } + function lineStartSmart(cm, pos) { + var start = lineStart(cm, pos.line); + var line = getLine(cm.doc, start.line); + var order = getOrder(line, cm.doc.direction); + if (!order || order[0].level == 0) { + var firstNonWS = Math.max(0, line.text.search(/\S/)); + var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch; + return Pos(start.line, inWS ? 0 : firstNonWS, start.sticky) + } + return start + } + + // Run a handler that was bound to a key. + function doHandleBinding(cm, bound, dropShift) { + if (typeof bound == "string") { + bound = commands[bound]; + if (!bound) { return false } + } + // Ensure previous input has been read, so that the handler sees a + // consistent view of the document + cm.display.input.ensurePolled(); + var prevShift = cm.display.shift, done = false; + try { + if (cm.isReadOnly()) { cm.state.suppressEdits = true; } + if (dropShift) { cm.display.shift = false; } + done = bound(cm) != Pass; + } finally { + cm.display.shift = prevShift; + cm.state.suppressEdits = false; + } + return done + } + + function lookupKeyForEditor(cm, name, handle) { + for (var i = 0; i < cm.state.keyMaps.length; i++) { + var result = lookupKey(name, cm.state.keyMaps[i], handle, cm); + if (result) { return result } + } + return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm)) + || lookupKey(name, cm.options.keyMap, handle, cm) + } + + // Note that, despite the name, this function is also used to check + // for bound mouse clicks. + + var stopSeq = new Delayed; + + function dispatchKey(cm, name, e, handle) { + var seq = cm.state.keySeq; + if (seq) { + if (isModifierKey(name)) { return "handled" } + if (/\'$/.test(name)) + { cm.state.keySeq = null; } + else + { stopSeq.set(50, function () { + if (cm.state.keySeq == seq) { + cm.state.keySeq = null; + cm.display.input.reset(); + } + }); } + if (dispatchKeyInner(cm, seq + " " + name, e, handle)) { return true } + } + return dispatchKeyInner(cm, name, e, handle) + } + + function dispatchKeyInner(cm, name, e, handle) { + var result = lookupKeyForEditor(cm, name, handle); + + if (result == "multi") + { cm.state.keySeq = name; } + if (result == "handled") + { signalLater(cm, "keyHandled", cm, name, e); } + + if (result == "handled" || result == "multi") { + e_preventDefault(e); + restartBlink(cm); + } + + return !!result + } + + // Handle a key from the keydown event. + function handleKeyBinding(cm, e) { + var name = keyName(e, true); + if (!name) { return false } + + if (e.shiftKey && !cm.state.keySeq) { + // First try to resolve full name (including 'Shift-'). Failing + // that, see if there is a cursor-motion command (starting with + // 'go') bound to the keyname without 'Shift-'. + return dispatchKey(cm, "Shift-" + name, e, function (b) { return doHandleBinding(cm, b, true); }) + || dispatchKey(cm, name, e, function (b) { + if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) + { return doHandleBinding(cm, b) } + }) + } else { + return dispatchKey(cm, name, e, function (b) { return doHandleBinding(cm, b); }) + } + } + + // Handle a key from the keypress event + function handleCharBinding(cm, e, ch) { + return dispatchKey(cm, "'" + ch + "'", e, function (b) { return doHandleBinding(cm, b, true); }) + } + + var lastStoppedKey = null; + function onKeyDown(e) { + var cm = this; + cm.curOp.focus = activeElt(); + if (signalDOMEvent(cm, e)) { return } + // IE does strange things with escape. + if (ie && ie_version < 11 && e.keyCode == 27) { e.returnValue = false; } + var code = e.keyCode; + cm.display.shift = code == 16 || e.shiftKey; + var handled = handleKeyBinding(cm, e); + if (presto) { + lastStoppedKey = handled ? code : null; + // Opera has no cut event... we try to at least catch the key combo + if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey)) + { cm.replaceSelection("", null, "cut"); } + } + + // Turn mouse into crosshair when Alt is held on Mac. + if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className)) + { showCrossHair(cm); } + } + + function showCrossHair(cm) { + var lineDiv = cm.display.lineDiv; + addClass(lineDiv, "CodeMirror-crosshair"); + + function up(e) { + if (e.keyCode == 18 || !e.altKey) { + rmClass(lineDiv, "CodeMirror-crosshair"); + off(document, "keyup", up); + off(document, "mouseover", up); + } + } + on(document, "keyup", up); + on(document, "mouseover", up); + } + + function onKeyUp(e) { + if (e.keyCode == 16) { this.doc.sel.shift = false; } + signalDOMEvent(this, e); + } + + function onKeyPress(e) { + var cm = this; + if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) { return } + var keyCode = e.keyCode, charCode = e.charCode; + if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return} + if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) { return } + var ch = String.fromCharCode(charCode == null ? keyCode : charCode); + // Some browsers fire keypress events for backspace + if (ch == "\x08") { return } + if (handleCharBinding(cm, e, ch)) { return } + cm.display.input.onKeyPress(e); + } + + var DOUBLECLICK_DELAY = 400; + + var PastClick = function(time, pos, button) { + this.time = time; + this.pos = pos; + this.button = button; + }; + + PastClick.prototype.compare = function (time, pos, button) { + return this.time + DOUBLECLICK_DELAY > time && + cmp(pos, this.pos) == 0 && button == this.button + }; + + var lastClick, lastDoubleClick; + function clickRepeat(pos, button) { + var now = +new Date; + if (lastDoubleClick && lastDoubleClick.compare(now, pos, button)) { + lastClick = lastDoubleClick = null; + return "triple" + } else if (lastClick && lastClick.compare(now, pos, button)) { + lastDoubleClick = new PastClick(now, pos, button); + lastClick = null; + return "double" + } else { + lastClick = new PastClick(now, pos, button); + lastDoubleClick = null; + return "single" + } + } + + // A mouse down can be a single click, double click, triple click, + // start of selection drag, start of text drag, new cursor + // (ctrl-click), rectangle drag (alt-drag), or xwin + // middle-click-paste. Or it might be a click on something we should + // not interfere with, such as a scrollbar or widget. + function onMouseDown(e) { + var cm = this, display = cm.display; + if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouch()) { return } + display.input.ensurePolled(); + display.shift = e.shiftKey; + + if (eventInWidget(display, e)) { + if (!webkit) { + // Briefly turn off draggability, to allow widgets to do + // normal dragging things. + display.scroller.draggable = false; + setTimeout(function () { return display.scroller.draggable = true; }, 100); + } + return + } + if (clickInGutter(cm, e)) { return } + var pos = posFromMouse(cm, e), button = e_button(e), repeat = pos ? clickRepeat(pos, button) : "single"; + window.focus(); + + // #3261: make sure, that we're not starting a second selection + if (button == 1 && cm.state.selectingText) + { cm.state.selectingText(e); } + + if (pos && handleMappedButton(cm, button, pos, repeat, e)) { return } + + if (button == 1) { + if (pos) { leftButtonDown(cm, pos, repeat, e); } + else if (e_target(e) == display.scroller) { e_preventDefault(e); } + } else if (button == 2) { + if (pos) { extendSelection(cm.doc, pos); } + setTimeout(function () { return display.input.focus(); }, 20); + } else if (button == 3) { + if (captureRightClick) { cm.display.input.onContextMenu(e); } + else { delayBlurEvent(cm); } + } + } + + function handleMappedButton(cm, button, pos, repeat, event) { + var name = "Click"; + if (repeat == "double") { name = "Double" + name; } + else if (repeat == "triple") { name = "Triple" + name; } + name = (button == 1 ? "Left" : button == 2 ? "Middle" : "Right") + name; + + return dispatchKey(cm, addModifierNames(name, event), event, function (bound) { + if (typeof bound == "string") { bound = commands[bound]; } + if (!bound) { return false } + var done = false; + try { + if (cm.isReadOnly()) { cm.state.suppressEdits = true; } + done = bound(cm, pos) != Pass; + } finally { + cm.state.suppressEdits = false; + } + return done + }) + } + + function configureMouse(cm, repeat, event) { + var option = cm.getOption("configureMouse"); + var value = option ? option(cm, repeat, event) : {}; + if (value.unit == null) { + var rect = chromeOS ? event.shiftKey && event.metaKey : event.altKey; + value.unit = rect ? "rectangle" : repeat == "single" ? "char" : repeat == "double" ? "word" : "line"; + } + if (value.extend == null || cm.doc.extend) { value.extend = cm.doc.extend || event.shiftKey; } + if (value.addNew == null) { value.addNew = mac ? event.metaKey : event.ctrlKey; } + if (value.moveOnDrag == null) { value.moveOnDrag = !(mac ? event.altKey : event.ctrlKey); } + return value + } + + function leftButtonDown(cm, pos, repeat, event) { + if (ie) { setTimeout(bind(ensureFocus, cm), 0); } + else { cm.curOp.focus = activeElt(); } + + var behavior = configureMouse(cm, repeat, event); + + var sel = cm.doc.sel, contained; + if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() && + repeat == "single" && (contained = sel.contains(pos)) > -1 && + (cmp((contained = sel.ranges[contained]).from(), pos) < 0 || pos.xRel > 0) && + (cmp(contained.to(), pos) > 0 || pos.xRel < 0)) + { leftButtonStartDrag(cm, event, pos, behavior); } + else + { leftButtonSelect(cm, event, pos, behavior); } + } + + // Start a text drag. When it ends, see if any dragging actually + // happen, and treat as a click if it didn't. + function leftButtonStartDrag(cm, event, pos, behavior) { + var display = cm.display, moved = false; + var dragEnd = operation(cm, function (e) { + if (webkit) { display.scroller.draggable = false; } + cm.state.draggingText = false; + off(display.wrapper.ownerDocument, "mouseup", dragEnd); + off(display.wrapper.ownerDocument, "mousemove", mouseMove); + off(display.scroller, "dragstart", dragStart); + off(display.scroller, "drop", dragEnd); + if (!moved) { + e_preventDefault(e); + if (!behavior.addNew) + { extendSelection(cm.doc, pos, null, null, behavior.extend); } + // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081) + if (webkit || ie && ie_version == 9) + { setTimeout(function () {display.wrapper.ownerDocument.body.focus(); display.input.focus();}, 20); } + else + { display.input.focus(); } + } + }); + var mouseMove = function(e2) { + moved = moved || Math.abs(event.clientX - e2.clientX) + Math.abs(event.clientY - e2.clientY) >= 10; + }; + var dragStart = function () { return moved = true; }; + // Let the drag handler handle this. + if (webkit) { display.scroller.draggable = true; } + cm.state.draggingText = dragEnd; + dragEnd.copy = !behavior.moveOnDrag; + // IE's approach to draggable + if (display.scroller.dragDrop) { display.scroller.dragDrop(); } + on(display.wrapper.ownerDocument, "mouseup", dragEnd); + on(display.wrapper.ownerDocument, "mousemove", mouseMove); + on(display.scroller, "dragstart", dragStart); + on(display.scroller, "drop", dragEnd); + + delayBlurEvent(cm); + setTimeout(function () { return display.input.focus(); }, 20); + } + + function rangeForUnit(cm, pos, unit) { + if (unit == "char") { return new Range(pos, pos) } + if (unit == "word") { return cm.findWordAt(pos) } + if (unit == "line") { return new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))) } + var result = unit(cm, pos); + return new Range(result.from, result.to) + } + + // Normal selection, as opposed to text dragging. + function leftButtonSelect(cm, event, start, behavior) { + var display = cm.display, doc = cm.doc; + e_preventDefault(event); + + var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges; + if (behavior.addNew && !behavior.extend) { + ourIndex = doc.sel.contains(start); + if (ourIndex > -1) + { ourRange = ranges[ourIndex]; } + else + { ourRange = new Range(start, start); } + } else { + ourRange = doc.sel.primary(); + ourIndex = doc.sel.primIndex; + } + + if (behavior.unit == "rectangle") { + if (!behavior.addNew) { ourRange = new Range(start, start); } + start = posFromMouse(cm, event, true, true); + ourIndex = -1; + } else { + var range$$1 = rangeForUnit(cm, start, behavior.unit); + if (behavior.extend) + { ourRange = extendRange(ourRange, range$$1.anchor, range$$1.head, behavior.extend); } + else + { ourRange = range$$1; } + } + + if (!behavior.addNew) { + ourIndex = 0; + setSelection(doc, new Selection([ourRange], 0), sel_mouse); + startSel = doc.sel; + } else if (ourIndex == -1) { + ourIndex = ranges.length; + setSelection(doc, normalizeSelection(cm, ranges.concat([ourRange]), ourIndex), + {scroll: false, origin: "*mouse"}); + } else if (ranges.length > 1 && ranges[ourIndex].empty() && behavior.unit == "char" && !behavior.extend) { + setSelection(doc, normalizeSelection(cm, ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0), + {scroll: false, origin: "*mouse"}); + startSel = doc.sel; + } else { + replaceOneSelection(doc, ourIndex, ourRange, sel_mouse); + } + + var lastPos = start; + function extendTo(pos) { + if (cmp(lastPos, pos) == 0) { return } + lastPos = pos; + + if (behavior.unit == "rectangle") { + var ranges = [], tabSize = cm.options.tabSize; + var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize); + var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize); + var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol); + for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line)); + line <= end; line++) { + var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize); + if (left == right) + { ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))); } + else if (text.length > leftPos) + { ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))); } + } + if (!ranges.length) { ranges.push(new Range(start, start)); } + setSelection(doc, normalizeSelection(cm, startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex), + {origin: "*mouse", scroll: false}); + cm.scrollIntoView(pos); + } else { + var oldRange = ourRange; + var range$$1 = rangeForUnit(cm, pos, behavior.unit); + var anchor = oldRange.anchor, head; + if (cmp(range$$1.anchor, anchor) > 0) { + head = range$$1.head; + anchor = minPos(oldRange.from(), range$$1.anchor); + } else { + head = range$$1.anchor; + anchor = maxPos(oldRange.to(), range$$1.head); + } + var ranges$1 = startSel.ranges.slice(0); + ranges$1[ourIndex] = bidiSimplify(cm, new Range(clipPos(doc, anchor), head)); + setSelection(doc, normalizeSelection(cm, ranges$1, ourIndex), sel_mouse); + } + } + + var editorSize = display.wrapper.getBoundingClientRect(); + // Used to ensure timeout re-tries don't fire when another extend + // happened in the meantime (clearTimeout isn't reliable -- at + // least on Chrome, the timeouts still happen even when cleared, + // if the clear happens after their scheduled firing time). + var counter = 0; + + function extend(e) { + var curCount = ++counter; + var cur = posFromMouse(cm, e, true, behavior.unit == "rectangle"); + if (!cur) { return } + if (cmp(cur, lastPos) != 0) { + cm.curOp.focus = activeElt(); + extendTo(cur); + var visible = visibleLines(display, doc); + if (cur.line >= visible.to || cur.line < visible.from) + { setTimeout(operation(cm, function () {if (counter == curCount) { extend(e); }}), 150); } + } else { + var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0; + if (outside) { setTimeout(operation(cm, function () { + if (counter != curCount) { return } + display.scroller.scrollTop += outside; + extend(e); + }), 50); } + } + } + + function done(e) { + cm.state.selectingText = false; + counter = Infinity; + e_preventDefault(e); + display.input.focus(); + off(display.wrapper.ownerDocument, "mousemove", move); + off(display.wrapper.ownerDocument, "mouseup", up); + doc.history.lastSelOrigin = null; + } + + var move = operation(cm, function (e) { + if (e.buttons === 0 || !e_button(e)) { done(e); } + else { extend(e); } + }); + var up = operation(cm, done); + cm.state.selectingText = up; + on(display.wrapper.ownerDocument, "mousemove", move); + on(display.wrapper.ownerDocument, "mouseup", up); + } + + // Used when mouse-selecting to adjust the anchor to the proper side + // of a bidi jump depending on the visual position of the head. + function bidiSimplify(cm, range$$1) { + var anchor = range$$1.anchor; + var head = range$$1.head; + var anchorLine = getLine(cm.doc, anchor.line); + if (cmp(anchor, head) == 0 && anchor.sticky == head.sticky) { return range$$1 } + var order = getOrder(anchorLine); + if (!order) { return range$$1 } + var index = getBidiPartAt(order, anchor.ch, anchor.sticky), part = order[index]; + if (part.from != anchor.ch && part.to != anchor.ch) { return range$$1 } + var boundary = index + ((part.from == anchor.ch) == (part.level != 1) ? 0 : 1); + if (boundary == 0 || boundary == order.length) { return range$$1 } + + // Compute the relative visual position of the head compared to the + // anchor (<0 is to the left, >0 to the right) + var leftSide; + if (head.line != anchor.line) { + leftSide = (head.line - anchor.line) * (cm.doc.direction == "ltr" ? 1 : -1) > 0; + } else { + var headIndex = getBidiPartAt(order, head.ch, head.sticky); + var dir = headIndex - index || (head.ch - anchor.ch) * (part.level == 1 ? -1 : 1); + if (headIndex == boundary - 1 || headIndex == boundary) + { leftSide = dir < 0; } + else + { leftSide = dir > 0; } + } + + var usePart = order[boundary + (leftSide ? -1 : 0)]; + var from = leftSide == (usePart.level == 1); + var ch = from ? usePart.from : usePart.to, sticky = from ? "after" : "before"; + return anchor.ch == ch && anchor.sticky == sticky ? range$$1 : new Range(new Pos(anchor.line, ch, sticky), head) + } + + + // Determines whether an event happened in the gutter, and fires the + // handlers for the corresponding event. + function gutterEvent(cm, e, type, prevent) { + var mX, mY; + if (e.touches) { + mX = e.touches[0].clientX; + mY = e.touches[0].clientY; + } else { + try { mX = e.clientX; mY = e.clientY; } + catch(e) { return false } + } + if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) { return false } + if (prevent) { e_preventDefault(e); } + + var display = cm.display; + var lineBox = display.lineDiv.getBoundingClientRect(); + + if (mY > lineBox.bottom || !hasHandler(cm, type)) { return e_defaultPrevented(e) } + mY -= lineBox.top - display.viewOffset; + + for (var i = 0; i < cm.options.gutters.length; ++i) { + var g = display.gutters.childNodes[i]; + if (g && g.getBoundingClientRect().right >= mX) { + var line = lineAtHeight(cm.doc, mY); + var gutter = cm.options.gutters[i]; + signal(cm, type, cm, line, gutter, e); + return e_defaultPrevented(e) + } + } + } + + function clickInGutter(cm, e) { + return gutterEvent(cm, e, "gutterClick", true) + } + + // CONTEXT MENU HANDLING + + // To make the context menu work, we need to briefly unhide the + // textarea (making it as unobtrusive as possible) to let the + // right-click take effect on it. + function onContextMenu(cm, e) { + if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) { return } + if (signalDOMEvent(cm, e, "contextmenu")) { return } + if (!captureRightClick) { cm.display.input.onContextMenu(e); } + } + + function contextMenuInGutter(cm, e) { + if (!hasHandler(cm, "gutterContextMenu")) { return false } + return gutterEvent(cm, e, "gutterContextMenu", false) + } + + function themeChanged(cm) { + cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + + cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-"); + clearCaches(cm); + } + + var Init = {toString: function(){return "CodeMirror.Init"}}; + + var defaults = {}; + var optionHandlers = {}; + + function defineOptions(CodeMirror) { + var optionHandlers = CodeMirror.optionHandlers; + + function option(name, deflt, handle, notOnInit) { + CodeMirror.defaults[name] = deflt; + if (handle) { optionHandlers[name] = + notOnInit ? function (cm, val, old) {if (old != Init) { handle(cm, val, old); }} : handle; } + } + + CodeMirror.defineOption = option; + + // Passed to option handlers when there is no old value. + CodeMirror.Init = Init; + + // These two are, on init, called from the constructor because they + // have to be initialized before the editor can start at all. + option("value", "", function (cm, val) { return cm.setValue(val); }, true); + option("mode", null, function (cm, val) { + cm.doc.modeOption = val; + loadMode(cm); + }, true); + + option("indentUnit", 2, loadMode, true); + option("indentWithTabs", false); + option("smartIndent", true); + option("tabSize", 4, function (cm) { + resetModeState(cm); + clearCaches(cm); + regChange(cm); + }, true); + + option("lineSeparator", null, function (cm, val) { + cm.doc.lineSep = val; + if (!val) { return } + var newBreaks = [], lineNo = cm.doc.first; + cm.doc.iter(function (line) { + for (var pos = 0;;) { + var found = line.text.indexOf(val, pos); + if (found == -1) { break } + pos = found + val.length; + newBreaks.push(Pos(lineNo, found)); + } + lineNo++; + }); + for (var i = newBreaks.length - 1; i >= 0; i--) + { replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length)); } + }); + option("specialChars", /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff]/g, function (cm, val, old) { + cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g"); + if (old != Init) { cm.refresh(); } + }); + option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function (cm) { return cm.refresh(); }, true); + option("electricChars", true); + option("inputStyle", mobile ? "contenteditable" : "textarea", function () { + throw new Error("inputStyle can not (yet) be changed in a running editor") // FIXME + }, true); + option("spellcheck", false, function (cm, val) { return cm.getInputField().spellcheck = val; }, true); + option("autocorrect", false, function (cm, val) { return cm.getInputField().autocorrect = val; }, true); + option("autocapitalize", false, function (cm, val) { return cm.getInputField().autocapitalize = val; }, true); + option("rtlMoveVisually", !windows); + option("wholeLineUpdateBefore", true); + + option("theme", "default", function (cm) { + themeChanged(cm); + guttersChanged(cm); + }, true); + option("keyMap", "default", function (cm, val, old) { + var next = getKeyMap(val); + var prev = old != Init && getKeyMap(old); + if (prev && prev.detach) { prev.detach(cm, next); } + if (next.attach) { next.attach(cm, prev || null); } + }); + option("extraKeys", null); + option("configureMouse", null); + + option("lineWrapping", false, wrappingChanged, true); + option("gutters", [], function (cm) { + setGuttersForLineNumbers(cm.options); + guttersChanged(cm); + }, true); + option("fixedGutter", true, function (cm, val) { + cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0"; + cm.refresh(); + }, true); + option("coverGutterNextToScrollbar", false, function (cm) { return updateScrollbars(cm); }, true); + option("scrollbarStyle", "native", function (cm) { + initScrollbars(cm); + updateScrollbars(cm); + cm.display.scrollbars.setScrollTop(cm.doc.scrollTop); + cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft); + }, true); + option("lineNumbers", false, function (cm) { + setGuttersForLineNumbers(cm.options); + guttersChanged(cm); + }, true); + option("firstLineNumber", 1, guttersChanged, true); + option("lineNumberFormatter", function (integer) { return integer; }, guttersChanged, true); + option("showCursorWhenSelecting", false, updateSelection, true); + + option("resetSelectionOnContextMenu", true); + option("lineWiseCopyCut", true); + option("pasteLinesPerSelection", true); + option("selectionsMayTouch", false); + + option("readOnly", false, function (cm, val) { + if (val == "nocursor") { + onBlur(cm); + cm.display.input.blur(); + } + cm.display.input.readOnlyChanged(val); + }); + option("disableInput", false, function (cm, val) {if (!val) { cm.display.input.reset(); }}, true); + option("dragDrop", true, dragDropChanged); + option("allowDropFileTypes", null); + + option("cursorBlinkRate", 530); + option("cursorScrollMargin", 0); + option("cursorHeight", 1, updateSelection, true); + option("singleCursorHeightPerLine", true, updateSelection, true); + option("workTime", 100); + option("workDelay", 100); + option("flattenSpans", true, resetModeState, true); + option("addModeClass", false, resetModeState, true); + option("pollInterval", 100); + option("undoDepth", 200, function (cm, val) { return cm.doc.history.undoDepth = val; }); + option("historyEventDelay", 1250); + option("viewportMargin", 10, function (cm) { return cm.refresh(); }, true); + option("maxHighlightLength", 10000, resetModeState, true); + option("moveInputWithCursor", true, function (cm, val) { + if (!val) { cm.display.input.resetPosition(); } + }); + + option("tabindex", null, function (cm, val) { return cm.display.input.getField().tabIndex = val || ""; }); + option("autofocus", null); + option("direction", "ltr", function (cm, val) { return cm.doc.setDirection(val); }, true); + option("phrases", null); + } + + function guttersChanged(cm) { + updateGutters(cm); + regChange(cm); + alignHorizontally(cm); + } + + function dragDropChanged(cm, value, old) { + var wasOn = old && old != Init; + if (!value != !wasOn) { + var funcs = cm.display.dragFunctions; + var toggle = value ? on : off; + toggle(cm.display.scroller, "dragstart", funcs.start); + toggle(cm.display.scroller, "dragenter", funcs.enter); + toggle(cm.display.scroller, "dragover", funcs.over); + toggle(cm.display.scroller, "dragleave", funcs.leave); + toggle(cm.display.scroller, "drop", funcs.drop); + } + } + + function wrappingChanged(cm) { + if (cm.options.lineWrapping) { + addClass(cm.display.wrapper, "CodeMirror-wrap"); + cm.display.sizer.style.minWidth = ""; + cm.display.sizerWidth = null; + } else { + rmClass(cm.display.wrapper, "CodeMirror-wrap"); + findMaxLine(cm); + } + estimateLineHeights(cm); + regChange(cm); + clearCaches(cm); + setTimeout(function () { return updateScrollbars(cm); }, 100); + } + + // A CodeMirror instance represents an editor. This is the object + // that user code is usually dealing with. + + function CodeMirror(place, options) { + var this$1 = this; + + if (!(this instanceof CodeMirror)) { return new CodeMirror(place, options) } + + this.options = options = options ? copyObj(options) : {}; + // Determine effective options based on given values and defaults. + copyObj(defaults, options, false); + setGuttersForLineNumbers(options); + + var doc = options.value; + if (typeof doc == "string") { doc = new Doc(doc, options.mode, null, options.lineSeparator, options.direction); } + else if (options.mode) { doc.modeOption = options.mode; } + this.doc = doc; + + var input = new CodeMirror.inputStyles[options.inputStyle](this); + var display = this.display = new Display(place, doc, input); + display.wrapper.CodeMirror = this; + updateGutters(this); + themeChanged(this); + if (options.lineWrapping) + { this.display.wrapper.className += " CodeMirror-wrap"; } + initScrollbars(this); + + this.state = { + keyMaps: [], // stores maps added by addKeyMap + overlays: [], // highlighting overlays, as added by addOverlay + modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info + overwrite: false, + delayingBlurEvent: false, + focused: false, + suppressEdits: false, // used to disable editing during key handlers when in readOnly mode + pasteIncoming: -1, cutIncoming: -1, // help recognize paste/cut edits in input.poll + selectingText: false, + draggingText: false, + highlight: new Delayed(), // stores highlight worker timeout + keySeq: null, // Unfinished key sequence + specialChars: null + }; + + if (options.autofocus && !mobile) { display.input.focus(); } + + // Override magic textarea content restore that IE sometimes does + // on our hidden textarea on reload + if (ie && ie_version < 11) { setTimeout(function () { return this$1.display.input.reset(true); }, 20); } + + registerEventHandlers(this); + ensureGlobalHandlers(); + + startOperation(this); + this.curOp.forceUpdate = true; + attachDoc(this, doc); + + if ((options.autofocus && !mobile) || this.hasFocus()) + { setTimeout(bind(onFocus, this), 20); } + else + { onBlur(this); } + + for (var opt in optionHandlers) { if (optionHandlers.hasOwnProperty(opt)) + { optionHandlers[opt](this$1, options[opt], Init); } } + maybeUpdateLineNumberWidth(this); + if (options.finishInit) { options.finishInit(this); } + for (var i = 0; i < initHooks.length; ++i) { initHooks[i](this$1); } + endOperation(this); + // Suppress optimizelegibility in Webkit, since it breaks text + // measuring on line wrapping boundaries. + if (webkit && options.lineWrapping && + getComputedStyle(display.lineDiv).textRendering == "optimizelegibility") + { display.lineDiv.style.textRendering = "auto"; } + } + + // The default configuration options. + CodeMirror.defaults = defaults; + // Functions to run when options are changed. + CodeMirror.optionHandlers = optionHandlers; + + // Attach the necessary event handlers when initializing the editor + function registerEventHandlers(cm) { + var d = cm.display; + on(d.scroller, "mousedown", operation(cm, onMouseDown)); + // Older IE's will not fire a second mousedown for a double click + if (ie && ie_version < 11) + { on(d.scroller, "dblclick", operation(cm, function (e) { + if (signalDOMEvent(cm, e)) { return } + var pos = posFromMouse(cm, e); + if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) { return } + e_preventDefault(e); + var word = cm.findWordAt(pos); + extendSelection(cm.doc, word.anchor, word.head); + })); } + else + { on(d.scroller, "dblclick", function (e) { return signalDOMEvent(cm, e) || e_preventDefault(e); }); } + // Some browsers fire contextmenu *after* opening the menu, at + // which point we can't mess with it anymore. Context menu is + // handled in onMouseDown for these browsers. + on(d.scroller, "contextmenu", function (e) { return onContextMenu(cm, e); }); + + // Used to suppress mouse event handling when a touch happens + var touchFinished, prevTouch = {end: 0}; + function finishTouch() { + if (d.activeTouch) { + touchFinished = setTimeout(function () { return d.activeTouch = null; }, 1000); + prevTouch = d.activeTouch; + prevTouch.end = +new Date; + } + } + function isMouseLikeTouchEvent(e) { + if (e.touches.length != 1) { return false } + var touch = e.touches[0]; + return touch.radiusX <= 1 && touch.radiusY <= 1 + } + function farAway(touch, other) { + if (other.left == null) { return true } + var dx = other.left - touch.left, dy = other.top - touch.top; + return dx * dx + dy * dy > 20 * 20 + } + on(d.scroller, "touchstart", function (e) { + if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e) && !clickInGutter(cm, e)) { + d.input.ensurePolled(); + clearTimeout(touchFinished); + var now = +new Date; + d.activeTouch = {start: now, moved: false, + prev: now - prevTouch.end <= 300 ? prevTouch : null}; + if (e.touches.length == 1) { + d.activeTouch.left = e.touches[0].pageX; + d.activeTouch.top = e.touches[0].pageY; + } + } + }); + on(d.scroller, "touchmove", function () { + if (d.activeTouch) { d.activeTouch.moved = true; } + }); + on(d.scroller, "touchend", function (e) { + var touch = d.activeTouch; + if (touch && !eventInWidget(d, e) && touch.left != null && + !touch.moved && new Date - touch.start < 300) { + var pos = cm.coordsChar(d.activeTouch, "page"), range; + if (!touch.prev || farAway(touch, touch.prev)) // Single tap + { range = new Range(pos, pos); } + else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap + { range = cm.findWordAt(pos); } + else // Triple tap + { range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))); } + cm.setSelection(range.anchor, range.head); + cm.focus(); + e_preventDefault(e); + } + finishTouch(); + }); + on(d.scroller, "touchcancel", finishTouch); + + // Sync scrolling between fake scrollbars and real scrollable + // area, ensure viewport is updated when scrolling. + on(d.scroller, "scroll", function () { + if (d.scroller.clientHeight) { + updateScrollTop(cm, d.scroller.scrollTop); + setScrollLeft(cm, d.scroller.scrollLeft, true); + signal(cm, "scroll", cm); + } + }); + + // Listen to wheel events in order to try and update the viewport on time. + on(d.scroller, "mousewheel", function (e) { return onScrollWheel(cm, e); }); + on(d.scroller, "DOMMouseScroll", function (e) { return onScrollWheel(cm, e); }); + + // Prevent wrapper from ever scrolling + on(d.wrapper, "scroll", function () { return d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }); + + d.dragFunctions = { + enter: function (e) {if (!signalDOMEvent(cm, e)) { e_stop(e); }}, + over: function (e) {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(e); }}, + start: function (e) { return onDragStart(cm, e); }, + drop: operation(cm, onDrop), + leave: function (e) {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm); }} + }; + + var inp = d.input.getField(); + on(inp, "keyup", function (e) { return onKeyUp.call(cm, e); }); + on(inp, "keydown", operation(cm, onKeyDown)); + on(inp, "keypress", operation(cm, onKeyPress)); + on(inp, "focus", function (e) { return onFocus(cm, e); }); + on(inp, "blur", function (e) { return onBlur(cm, e); }); + } + + var initHooks = []; + CodeMirror.defineInitHook = function (f) { return initHooks.push(f); }; + + // Indent the given line. The how parameter can be "smart", + // "add"/null, "subtract", or "prev". When aggressive is false + // (typically set to true for forced single-line indents), empty + // lines are not indented, and places where the mode returns Pass + // are left alone. + function indentLine(cm, n, how, aggressive) { + var doc = cm.doc, state; + if (how == null) { how = "add"; } + if (how == "smart") { + // Fall back to "prev" when the mode doesn't have an indentation + // method. + if (!doc.mode.indent) { how = "prev"; } + else { state = getContextBefore(cm, n).state; } + } + + var tabSize = cm.options.tabSize; + var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize); + if (line.stateAfter) { line.stateAfter = null; } + var curSpaceString = line.text.match(/^\s*/)[0], indentation; + if (!aggressive && !/\S/.test(line.text)) { + indentation = 0; + how = "not"; + } else if (how == "smart") { + indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text); + if (indentation == Pass || indentation > 150) { + if (!aggressive) { return } + how = "prev"; + } + } + if (how == "prev") { + if (n > doc.first) { indentation = countColumn(getLine(doc, n-1).text, null, tabSize); } + else { indentation = 0; } + } else if (how == "add") { + indentation = curSpace + cm.options.indentUnit; + } else if (how == "subtract") { + indentation = curSpace - cm.options.indentUnit; + } else if (typeof how == "number") { + indentation = curSpace + how; + } + indentation = Math.max(0, indentation); + + var indentString = "", pos = 0; + if (cm.options.indentWithTabs) + { for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";} } + if (pos < indentation) { indentString += spaceStr(indentation - pos); } + + if (indentString != curSpaceString) { + replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input"); + line.stateAfter = null; + return true + } else { + // Ensure that, if the cursor was in the whitespace at the start + // of the line, it is moved to the end of that space. + for (var i$1 = 0; i$1 < doc.sel.ranges.length; i$1++) { + var range = doc.sel.ranges[i$1]; + if (range.head.line == n && range.head.ch < curSpaceString.length) { + var pos$1 = Pos(n, curSpaceString.length); + replaceOneSelection(doc, i$1, new Range(pos$1, pos$1)); + break + } + } + } + } + + // This will be set to a {lineWise: bool, text: [string]} object, so + // that, when pasting, we know what kind of selections the copied + // text was made out of. + var lastCopied = null; + + function setLastCopied(newLastCopied) { + lastCopied = newLastCopied; + } + + function applyTextInput(cm, inserted, deleted, sel, origin) { + var doc = cm.doc; + cm.display.shift = false; + if (!sel) { sel = doc.sel; } + + var recent = +new Date - 200; + var paste = origin == "paste" || cm.state.pasteIncoming > recent; + var textLines = splitLinesAuto(inserted), multiPaste = null; + // When pasting N lines into N selections, insert one line per selection + if (paste && sel.ranges.length > 1) { + if (lastCopied && lastCopied.text.join("\n") == inserted) { + if (sel.ranges.length % lastCopied.text.length == 0) { + multiPaste = []; + for (var i = 0; i < lastCopied.text.length; i++) + { multiPaste.push(doc.splitLines(lastCopied.text[i])); } + } + } else if (textLines.length == sel.ranges.length && cm.options.pasteLinesPerSelection) { + multiPaste = map(textLines, function (l) { return [l]; }); + } + } + + var updateInput = cm.curOp.updateInput; + // Normal behavior is to insert the new text into every selection + for (var i$1 = sel.ranges.length - 1; i$1 >= 0; i$1--) { + var range$$1 = sel.ranges[i$1]; + var from = range$$1.from(), to = range$$1.to(); + if (range$$1.empty()) { + if (deleted && deleted > 0) // Handle deletion + { from = Pos(from.line, from.ch - deleted); } + else if (cm.state.overwrite && !paste) // Handle overwrite + { to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)); } + else if (paste && lastCopied && lastCopied.lineWise && lastCopied.text.join("\n") == inserted) + { from = to = Pos(from.line, 0); } + } + var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i$1 % multiPaste.length] : textLines, + origin: origin || (paste ? "paste" : cm.state.cutIncoming > recent ? "cut" : "+input")}; + makeChange(cm.doc, changeEvent); + signalLater(cm, "inputRead", cm, changeEvent); + } + if (inserted && !paste) + { triggerElectric(cm, inserted); } + + ensureCursorVisible(cm); + if (cm.curOp.updateInput < 2) { cm.curOp.updateInput = updateInput; } + cm.curOp.typing = true; + cm.state.pasteIncoming = cm.state.cutIncoming = -1; + } + + function handlePaste(e, cm) { + var pasted = e.clipboardData && e.clipboardData.getData("Text"); + if (pasted) { + e.preventDefault(); + if (!cm.isReadOnly() && !cm.options.disableInput) + { runInOp(cm, function () { return applyTextInput(cm, pasted, 0, null, "paste"); }); } + return true + } + } + + function triggerElectric(cm, inserted) { + // When an 'electric' character is inserted, immediately trigger a reindent + if (!cm.options.electricChars || !cm.options.smartIndent) { return } + var sel = cm.doc.sel; + + for (var i = sel.ranges.length - 1; i >= 0; i--) { + var range$$1 = sel.ranges[i]; + if (range$$1.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range$$1.head.line)) { continue } + var mode = cm.getModeAt(range$$1.head); + var indented = false; + if (mode.electricChars) { + for (var j = 0; j < mode.electricChars.length; j++) + { if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) { + indented = indentLine(cm, range$$1.head.line, "smart"); + break + } } + } else if (mode.electricInput) { + if (mode.electricInput.test(getLine(cm.doc, range$$1.head.line).text.slice(0, range$$1.head.ch))) + { indented = indentLine(cm, range$$1.head.line, "smart"); } + } + if (indented) { signalLater(cm, "electricInput", cm, range$$1.head.line); } + } + } + + function copyableRanges(cm) { + var text = [], ranges = []; + for (var i = 0; i < cm.doc.sel.ranges.length; i++) { + var line = cm.doc.sel.ranges[i].head.line; + var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)}; + ranges.push(lineRange); + text.push(cm.getRange(lineRange.anchor, lineRange.head)); + } + return {text: text, ranges: ranges} + } + + function disableBrowserMagic(field, spellcheck, autocorrect, autocapitalize) { + field.setAttribute("autocorrect", !!autocorrect); + field.setAttribute("autocapitalize", !!autocapitalize); + field.setAttribute("spellcheck", !!spellcheck); + } + + function hiddenTextarea() { + var te = elt("textarea", null, null, "position: absolute; bottom: -1em; padding: 0; width: 1px; height: 1em; outline: none"); + var div = elt("div", [te], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); + // The textarea is kept positioned near the cursor to prevent the + // fact that it'll be scrolled into view on input from scrolling + // our fake cursor out of view. On webkit, when wrap=off, paste is + // very slow. So make the area wide instead. + if (webkit) { te.style.width = "1000px"; } + else { te.setAttribute("wrap", "off"); } + // If border: 0; -- iOS fails to open keyboard (issue #1287) + if (ios) { te.style.border = "1px solid black"; } + disableBrowserMagic(te); + return div + } + + // The publicly visible API. Note that methodOp(f) means + // 'wrap f in an operation, performed on its `this` parameter'. + + // This is not the complete set of editor methods. Most of the + // methods defined on the Doc type are also injected into + // CodeMirror.prototype, for backwards compatibility and + // convenience. + + function addEditorMethods(CodeMirror) { + var optionHandlers = CodeMirror.optionHandlers; + + var helpers = CodeMirror.helpers = {}; + + CodeMirror.prototype = { + constructor: CodeMirror, + focus: function(){window.focus(); this.display.input.focus();}, + + setOption: function(option, value) { + var options = this.options, old = options[option]; + if (options[option] == value && option != "mode") { return } + options[option] = value; + if (optionHandlers.hasOwnProperty(option)) + { operation(this, optionHandlers[option])(this, value, old); } + signal(this, "optionChange", this, option); + }, + + getOption: function(option) {return this.options[option]}, + getDoc: function() {return this.doc}, + + addKeyMap: function(map$$1, bottom) { + this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map$$1)); + }, + removeKeyMap: function(map$$1) { + var maps = this.state.keyMaps; + for (var i = 0; i < maps.length; ++i) + { if (maps[i] == map$$1 || maps[i].name == map$$1) { + maps.splice(i, 1); + return true + } } + }, + + addOverlay: methodOp(function(spec, options) { + var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec); + if (mode.startState) { throw new Error("Overlays may not be stateful.") } + insertSorted(this.state.overlays, + {mode: mode, modeSpec: spec, opaque: options && options.opaque, + priority: (options && options.priority) || 0}, + function (overlay) { return overlay.priority; }); + this.state.modeGen++; + regChange(this); + }), + removeOverlay: methodOp(function(spec) { + var this$1 = this; + + var overlays = this.state.overlays; + for (var i = 0; i < overlays.length; ++i) { + var cur = overlays[i].modeSpec; + if (cur == spec || typeof spec == "string" && cur.name == spec) { + overlays.splice(i, 1); + this$1.state.modeGen++; + regChange(this$1); + return + } + } + }), + + indentLine: methodOp(function(n, dir, aggressive) { + if (typeof dir != "string" && typeof dir != "number") { + if (dir == null) { dir = this.options.smartIndent ? "smart" : "prev"; } + else { dir = dir ? "add" : "subtract"; } + } + if (isLine(this.doc, n)) { indentLine(this, n, dir, aggressive); } + }), + indentSelection: methodOp(function(how) { + var this$1 = this; + + var ranges = this.doc.sel.ranges, end = -1; + for (var i = 0; i < ranges.length; i++) { + var range$$1 = ranges[i]; + if (!range$$1.empty()) { + var from = range$$1.from(), to = range$$1.to(); + var start = Math.max(end, from.line); + end = Math.min(this$1.lastLine(), to.line - (to.ch ? 0 : 1)) + 1; + for (var j = start; j < end; ++j) + { indentLine(this$1, j, how); } + var newRanges = this$1.doc.sel.ranges; + if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0) + { replaceOneSelection(this$1.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll); } + } else if (range$$1.head.line > end) { + indentLine(this$1, range$$1.head.line, how, true); + end = range$$1.head.line; + if (i == this$1.doc.sel.primIndex) { ensureCursorVisible(this$1); } + } + } + }), + + // Fetch the parser token for a given character. Useful for hacks + // that want to inspect the mode state (say, for completion). + getTokenAt: function(pos, precise) { + return takeToken(this, pos, precise) + }, + + getLineTokens: function(line, precise) { + return takeToken(this, Pos(line), precise, true) + }, + + getTokenTypeAt: function(pos) { + pos = clipPos(this.doc, pos); + var styles = getLineStyles(this, getLine(this.doc, pos.line)); + var before = 0, after = (styles.length - 1) / 2, ch = pos.ch; + var type; + if (ch == 0) { type = styles[2]; } + else { for (;;) { + var mid = (before + after) >> 1; + if ((mid ? styles[mid * 2 - 1] : 0) >= ch) { after = mid; } + else if (styles[mid * 2 + 1] < ch) { before = mid + 1; } + else { type = styles[mid * 2 + 2]; break } + } } + var cut = type ? type.indexOf("overlay ") : -1; + return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1) + }, + + getModeAt: function(pos) { + var mode = this.doc.mode; + if (!mode.innerMode) { return mode } + return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode + }, + + getHelper: function(pos, type) { + return this.getHelpers(pos, type)[0] + }, + + getHelpers: function(pos, type) { + var this$1 = this; + + var found = []; + if (!helpers.hasOwnProperty(type)) { return found } + var help = helpers[type], mode = this.getModeAt(pos); + if (typeof mode[type] == "string") { + if (help[mode[type]]) { found.push(help[mode[type]]); } + } else if (mode[type]) { + for (var i = 0; i < mode[type].length; i++) { + var val = help[mode[type][i]]; + if (val) { found.push(val); } + } + } else if (mode.helperType && help[mode.helperType]) { + found.push(help[mode.helperType]); + } else if (help[mode.name]) { + found.push(help[mode.name]); + } + for (var i$1 = 0; i$1 < help._global.length; i$1++) { + var cur = help._global[i$1]; + if (cur.pred(mode, this$1) && indexOf(found, cur.val) == -1) + { found.push(cur.val); } + } + return found + }, + + getStateAfter: function(line, precise) { + var doc = this.doc; + line = clipLine(doc, line == null ? doc.first + doc.size - 1: line); + return getContextBefore(this, line + 1, precise).state + }, + + cursorCoords: function(start, mode) { + var pos, range$$1 = this.doc.sel.primary(); + if (start == null) { pos = range$$1.head; } + else if (typeof start == "object") { pos = clipPos(this.doc, start); } + else { pos = start ? range$$1.from() : range$$1.to(); } + return cursorCoords(this, pos, mode || "page") + }, + + charCoords: function(pos, mode) { + return charCoords(this, clipPos(this.doc, pos), mode || "page") + }, + + coordsChar: function(coords, mode) { + coords = fromCoordSystem(this, coords, mode || "page"); + return coordsChar(this, coords.left, coords.top) + }, + + lineAtHeight: function(height, mode) { + height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top; + return lineAtHeight(this.doc, height + this.display.viewOffset) + }, + heightAtLine: function(line, mode, includeWidgets) { + var end = false, lineObj; + if (typeof line == "number") { + var last = this.doc.first + this.doc.size - 1; + if (line < this.doc.first) { line = this.doc.first; } + else if (line > last) { line = last; end = true; } + lineObj = getLine(this.doc, line); + } else { + lineObj = line; + } + return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page", includeWidgets || end).top + + (end ? this.doc.height - heightAtLine(lineObj) : 0) + }, + + defaultTextHeight: function() { return textHeight(this.display) }, + defaultCharWidth: function() { return charWidth(this.display) }, + + getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo}}, + + addWidget: function(pos, node, scroll, vert, horiz) { + var display = this.display; + pos = cursorCoords(this, clipPos(this.doc, pos)); + var top = pos.bottom, left = pos.left; + node.style.position = "absolute"; + node.setAttribute("cm-ignore-events", "true"); + this.display.input.setUneditable(node); + display.sizer.appendChild(node); + if (vert == "over") { + top = pos.top; + } else if (vert == "above" || vert == "near") { + var vspace = Math.max(display.wrapper.clientHeight, this.doc.height), + hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth); + // Default to positioning above (if specified and possible); otherwise default to positioning below + if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight) + { top = pos.top - node.offsetHeight; } + else if (pos.bottom + node.offsetHeight <= vspace) + { top = pos.bottom; } + if (left + node.offsetWidth > hspace) + { left = hspace - node.offsetWidth; } + } + node.style.top = top + "px"; + node.style.left = node.style.right = ""; + if (horiz == "right") { + left = display.sizer.clientWidth - node.offsetWidth; + node.style.right = "0px"; + } else { + if (horiz == "left") { left = 0; } + else if (horiz == "middle") { left = (display.sizer.clientWidth - node.offsetWidth) / 2; } + node.style.left = left + "px"; + } + if (scroll) + { scrollIntoView(this, {left: left, top: top, right: left + node.offsetWidth, bottom: top + node.offsetHeight}); } + }, + + triggerOnKeyDown: methodOp(onKeyDown), + triggerOnKeyPress: methodOp(onKeyPress), + triggerOnKeyUp: onKeyUp, + triggerOnMouseDown: methodOp(onMouseDown), + + execCommand: function(cmd) { + if (commands.hasOwnProperty(cmd)) + { return commands[cmd].call(null, this) } + }, + + triggerElectric: methodOp(function(text) { triggerElectric(this, text); }), + + findPosH: function(from, amount, unit, visually) { + var this$1 = this; + + var dir = 1; + if (amount < 0) { dir = -1; amount = -amount; } + var cur = clipPos(this.doc, from); + for (var i = 0; i < amount; ++i) { + cur = findPosH(this$1.doc, cur, dir, unit, visually); + if (cur.hitSide) { break } + } + return cur + }, + + moveH: methodOp(function(dir, unit) { + var this$1 = this; + + this.extendSelectionsBy(function (range$$1) { + if (this$1.display.shift || this$1.doc.extend || range$$1.empty()) + { return findPosH(this$1.doc, range$$1.head, dir, unit, this$1.options.rtlMoveVisually) } + else + { return dir < 0 ? range$$1.from() : range$$1.to() } + }, sel_move); + }), + + deleteH: methodOp(function(dir, unit) { + var sel = this.doc.sel, doc = this.doc; + if (sel.somethingSelected()) + { doc.replaceSelection("", null, "+delete"); } + else + { deleteNearSelection(this, function (range$$1) { + var other = findPosH(doc, range$$1.head, dir, unit, false); + return dir < 0 ? {from: other, to: range$$1.head} : {from: range$$1.head, to: other} + }); } + }), + + findPosV: function(from, amount, unit, goalColumn) { + var this$1 = this; + + var dir = 1, x = goalColumn; + if (amount < 0) { dir = -1; amount = -amount; } + var cur = clipPos(this.doc, from); + for (var i = 0; i < amount; ++i) { + var coords = cursorCoords(this$1, cur, "div"); + if (x == null) { x = coords.left; } + else { coords.left = x; } + cur = findPosV(this$1, coords, dir, unit); + if (cur.hitSide) { break } + } + return cur + }, + + moveV: methodOp(function(dir, unit) { + var this$1 = this; + + var doc = this.doc, goals = []; + var collapse = !this.display.shift && !doc.extend && doc.sel.somethingSelected(); + doc.extendSelectionsBy(function (range$$1) { + if (collapse) + { return dir < 0 ? range$$1.from() : range$$1.to() } + var headPos = cursorCoords(this$1, range$$1.head, "div"); + if (range$$1.goalColumn != null) { headPos.left = range$$1.goalColumn; } + goals.push(headPos.left); + var pos = findPosV(this$1, headPos, dir, unit); + if (unit == "page" && range$$1 == doc.sel.primary()) + { addToScrollTop(this$1, charCoords(this$1, pos, "div").top - headPos.top); } + return pos + }, sel_move); + if (goals.length) { for (var i = 0; i < doc.sel.ranges.length; i++) + { doc.sel.ranges[i].goalColumn = goals[i]; } } + }), + + // Find the word at the given position (as returned by coordsChar). + findWordAt: function(pos) { + var doc = this.doc, line = getLine(doc, pos.line).text; + var start = pos.ch, end = pos.ch; + if (line) { + var helper = this.getHelper(pos, "wordChars"); + if ((pos.sticky == "before" || end == line.length) && start) { --start; } else { ++end; } + var startChar = line.charAt(start); + var check = isWordChar(startChar, helper) + ? function (ch) { return isWordChar(ch, helper); } + : /\s/.test(startChar) ? function (ch) { return /\s/.test(ch); } + : function (ch) { return (!/\s/.test(ch) && !isWordChar(ch)); }; + while (start > 0 && check(line.charAt(start - 1))) { --start; } + while (end < line.length && check(line.charAt(end))) { ++end; } + } + return new Range(Pos(pos.line, start), Pos(pos.line, end)) + }, + + toggleOverwrite: function(value) { + if (value != null && value == this.state.overwrite) { return } + if (this.state.overwrite = !this.state.overwrite) + { addClass(this.display.cursorDiv, "CodeMirror-overwrite"); } + else + { rmClass(this.display.cursorDiv, "CodeMirror-overwrite"); } + + signal(this, "overwriteToggle", this, this.state.overwrite); + }, + hasFocus: function() { return this.display.input.getField() == activeElt() }, + isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit) }, + + scrollTo: methodOp(function (x, y) { scrollToCoords(this, x, y); }), + getScrollInfo: function() { + var scroller = this.display.scroller; + return {left: scroller.scrollLeft, top: scroller.scrollTop, + height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight, + width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth, + clientHeight: displayHeight(this), clientWidth: displayWidth(this)} + }, + + scrollIntoView: methodOp(function(range$$1, margin) { + if (range$$1 == null) { + range$$1 = {from: this.doc.sel.primary().head, to: null}; + if (margin == null) { margin = this.options.cursorScrollMargin; } + } else if (typeof range$$1 == "number") { + range$$1 = {from: Pos(range$$1, 0), to: null}; + } else if (range$$1.from == null) { + range$$1 = {from: range$$1, to: null}; + } + if (!range$$1.to) { range$$1.to = range$$1.from; } + range$$1.margin = margin || 0; + + if (range$$1.from.line != null) { + scrollToRange(this, range$$1); + } else { + scrollToCoordsRange(this, range$$1.from, range$$1.to, range$$1.margin); + } + }), + + setSize: methodOp(function(width, height) { + var this$1 = this; + + var interpret = function (val) { return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; }; + if (width != null) { this.display.wrapper.style.width = interpret(width); } + if (height != null) { this.display.wrapper.style.height = interpret(height); } + if (this.options.lineWrapping) { clearLineMeasurementCache(this); } + var lineNo$$1 = this.display.viewFrom; + this.doc.iter(lineNo$$1, this.display.viewTo, function (line) { + if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) + { if (line.widgets[i].noHScroll) { regLineChange(this$1, lineNo$$1, "widget"); break } } } + ++lineNo$$1; + }); + this.curOp.forceUpdate = true; + signal(this, "refresh", this); + }), + + operation: function(f){return runInOp(this, f)}, + startOperation: function(){return startOperation(this)}, + endOperation: function(){return endOperation(this)}, + + refresh: methodOp(function() { + var oldHeight = this.display.cachedTextHeight; + regChange(this); + this.curOp.forceUpdate = true; + clearCaches(this); + scrollToCoords(this, this.doc.scrollLeft, this.doc.scrollTop); + updateGutterSpace(this); + if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5) + { estimateLineHeights(this); } + signal(this, "refresh", this); + }), + + swapDoc: methodOp(function(doc) { + var old = this.doc; + old.cm = null; + attachDoc(this, doc); + clearCaches(this); + this.display.input.reset(); + scrollToCoords(this, doc.scrollLeft, doc.scrollTop); + this.curOp.forceScroll = true; + signalLater(this, "swapDoc", this, old); + return old + }), + + phrase: function(phraseText) { + var phrases = this.options.phrases; + return phrases && Object.prototype.hasOwnProperty.call(phrases, phraseText) ? phrases[phraseText] : phraseText + }, + + getInputField: function(){return this.display.input.getField()}, + getWrapperElement: function(){return this.display.wrapper}, + getScrollerElement: function(){return this.display.scroller}, + getGutterElement: function(){return this.display.gutters} + }; + eventMixin(CodeMirror); + + CodeMirror.registerHelper = function(type, name, value) { + if (!helpers.hasOwnProperty(type)) { helpers[type] = CodeMirror[type] = {_global: []}; } + helpers[type][name] = value; + }; + CodeMirror.registerGlobalHelper = function(type, name, predicate, value) { + CodeMirror.registerHelper(type, name, value); + helpers[type]._global.push({pred: predicate, val: value}); + }; + } + + // Used for horizontal relative motion. Dir is -1 or 1 (left or + // right), unit can be "char", "column" (like char, but doesn't + // cross line boundaries), "word" (across next word), or "group" (to + // the start of next group of word or non-word-non-whitespace + // chars). The visually param controls whether, in right-to-left + // text, direction 1 means to move towards the next index in the + // string, or towards the character to the right of the current + // position. The resulting position will have a hitSide=true + // property if it reached the end of the document. + function findPosH(doc, pos, dir, unit, visually) { + var oldPos = pos; + var origDir = dir; + var lineObj = getLine(doc, pos.line); + function findNextLine() { + var l = pos.line + dir; + if (l < doc.first || l >= doc.first + doc.size) { return false } + pos = new Pos(l, pos.ch, pos.sticky); + return lineObj = getLine(doc, l) + } + function moveOnce(boundToLine) { + var next; + if (visually) { + next = moveVisually(doc.cm, lineObj, pos, dir); + } else { + next = moveLogically(lineObj, pos, dir); + } + if (next == null) { + if (!boundToLine && findNextLine()) + { pos = endOfLine(visually, doc.cm, lineObj, pos.line, dir); } + else + { return false } + } else { + pos = next; + } + return true + } + + if (unit == "char") { + moveOnce(); + } else if (unit == "column") { + moveOnce(true); + } else if (unit == "word" || unit == "group") { + var sawType = null, group = unit == "group"; + var helper = doc.cm && doc.cm.getHelper(pos, "wordChars"); + for (var first = true;; first = false) { + if (dir < 0 && !moveOnce(!first)) { break } + var cur = lineObj.text.charAt(pos.ch) || "\n"; + var type = isWordChar(cur, helper) ? "w" + : group && cur == "\n" ? "n" + : !group || /\s/.test(cur) ? null + : "p"; + if (group && !first && !type) { type = "s"; } + if (sawType && sawType != type) { + if (dir < 0) {dir = 1; moveOnce(); pos.sticky = "after";} + break + } + + if (type) { sawType = type; } + if (dir > 0 && !moveOnce(!first)) { break } + } + } + var result = skipAtomic(doc, pos, oldPos, origDir, true); + if (equalCursorPos(oldPos, result)) { result.hitSide = true; } + return result + } + + // For relative vertical movement. Dir may be -1 or 1. Unit can be + // "page" or "line". The resulting position will have a hitSide=true + // property if it reached the end of the document. + function findPosV(cm, pos, dir, unit) { + var doc = cm.doc, x = pos.left, y; + if (unit == "page") { + var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight); + var moveAmount = Math.max(pageSize - .5 * textHeight(cm.display), 3); + y = (dir > 0 ? pos.bottom : pos.top) + dir * moveAmount; + + } else if (unit == "line") { + y = dir > 0 ? pos.bottom + 3 : pos.top - 3; + } + var target; + for (;;) { + target = coordsChar(cm, x, y); + if (!target.outside) { break } + if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break } + y += dir * 5; + } + return target + } + + // CONTENTEDITABLE INPUT STYLE + + var ContentEditableInput = function(cm) { + this.cm = cm; + this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null; + this.polling = new Delayed(); + this.composing = null; + this.gracePeriod = false; + this.readDOMTimeout = null; + }; + + ContentEditableInput.prototype.init = function (display) { + var this$1 = this; + + var input = this, cm = input.cm; + var div = input.div = display.lineDiv; + disableBrowserMagic(div, cm.options.spellcheck, cm.options.autocorrect, cm.options.autocapitalize); + + on(div, "paste", function (e) { + if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return } + // IE doesn't fire input events, so we schedule a read for the pasted content in this way + if (ie_version <= 11) { setTimeout(operation(cm, function () { return this$1.updateFromDOM(); }), 20); } + }); + + on(div, "compositionstart", function (e) { + this$1.composing = {data: e.data, done: false}; + }); + on(div, "compositionupdate", function (e) { + if (!this$1.composing) { this$1.composing = {data: e.data, done: false}; } + }); + on(div, "compositionend", function (e) { + if (this$1.composing) { + if (e.data != this$1.composing.data) { this$1.readFromDOMSoon(); } + this$1.composing.done = true; + } + }); + + on(div, "touchstart", function () { return input.forceCompositionEnd(); }); + + on(div, "input", function () { + if (!this$1.composing) { this$1.readFromDOMSoon(); } + }); + + function onCopyCut(e) { + if (signalDOMEvent(cm, e)) { return } + if (cm.somethingSelected()) { + setLastCopied({lineWise: false, text: cm.getSelections()}); + if (e.type == "cut") { cm.replaceSelection("", null, "cut"); } + } else if (!cm.options.lineWiseCopyCut) { + return + } else { + var ranges = copyableRanges(cm); + setLastCopied({lineWise: true, text: ranges.text}); + if (e.type == "cut") { + cm.operation(function () { + cm.setSelections(ranges.ranges, 0, sel_dontScroll); + cm.replaceSelection("", null, "cut"); + }); + } + } + if (e.clipboardData) { + e.clipboardData.clearData(); + var content = lastCopied.text.join("\n"); + // iOS exposes the clipboard API, but seems to discard content inserted into it + e.clipboardData.setData("Text", content); + if (e.clipboardData.getData("Text") == content) { + e.preventDefault(); + return + } + } + // Old-fashioned briefly-focus-a-textarea hack + var kludge = hiddenTextarea(), te = kludge.firstChild; + cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild); + te.value = lastCopied.text.join("\n"); + var hadFocus = document.activeElement; + selectInput(te); + setTimeout(function () { + cm.display.lineSpace.removeChild(kludge); + hadFocus.focus(); + if (hadFocus == div) { input.showPrimarySelection(); } + }, 50); + } + on(div, "copy", onCopyCut); + on(div, "cut", onCopyCut); + }; + + ContentEditableInput.prototype.prepareSelection = function () { + var result = prepareSelection(this.cm, false); + result.focus = this.cm.state.focused; + return result + }; + + ContentEditableInput.prototype.showSelection = function (info, takeFocus) { + if (!info || !this.cm.display.view.length) { return } + if (info.focus || takeFocus) { this.showPrimarySelection(); } + this.showMultipleSelections(info); + }; + + ContentEditableInput.prototype.getSelection = function () { + return this.cm.display.wrapper.ownerDocument.getSelection() + }; + + ContentEditableInput.prototype.showPrimarySelection = function () { + var sel = this.getSelection(), cm = this.cm, prim = cm.doc.sel.primary(); + var from = prim.from(), to = prim.to(); + + if (cm.display.viewTo == cm.display.viewFrom || from.line >= cm.display.viewTo || to.line < cm.display.viewFrom) { + sel.removeAllRanges(); + return + } + + var curAnchor = domToPos(cm, sel.anchorNode, sel.anchorOffset); + var curFocus = domToPos(cm, sel.focusNode, sel.focusOffset); + if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad && + cmp(minPos(curAnchor, curFocus), from) == 0 && + cmp(maxPos(curAnchor, curFocus), to) == 0) + { return } + + var view = cm.display.view; + var start = (from.line >= cm.display.viewFrom && posToDOM(cm, from)) || + {node: view[0].measure.map[2], offset: 0}; + var end = to.line < cm.display.viewTo && posToDOM(cm, to); + if (!end) { + var measure = view[view.length - 1].measure; + var map$$1 = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map; + end = {node: map$$1[map$$1.length - 1], offset: map$$1[map$$1.length - 2] - map$$1[map$$1.length - 3]}; + } + + if (!start || !end) { + sel.removeAllRanges(); + return + } + + var old = sel.rangeCount && sel.getRangeAt(0), rng; + try { rng = range(start.node, start.offset, end.offset, end.node); } + catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible + if (rng) { + if (!gecko && cm.state.focused) { + sel.collapse(start.node, start.offset); + if (!rng.collapsed) { + sel.removeAllRanges(); + sel.addRange(rng); + } + } else { + sel.removeAllRanges(); + sel.addRange(rng); + } + if (old && sel.anchorNode == null) { sel.addRange(old); } + else if (gecko) { this.startGracePeriod(); } + } + this.rememberSelection(); + }; + + ContentEditableInput.prototype.startGracePeriod = function () { + var this$1 = this; + + clearTimeout(this.gracePeriod); + this.gracePeriod = setTimeout(function () { + this$1.gracePeriod = false; + if (this$1.selectionChanged()) + { this$1.cm.operation(function () { return this$1.cm.curOp.selectionChanged = true; }); } + }, 20); + }; + + ContentEditableInput.prototype.showMultipleSelections = function (info) { + removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors); + removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection); + }; + + ContentEditableInput.prototype.rememberSelection = function () { + var sel = this.getSelection(); + this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset; + this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset; + }; + + ContentEditableInput.prototype.selectionInEditor = function () { + var sel = this.getSelection(); + if (!sel.rangeCount) { return false } + var node = sel.getRangeAt(0).commonAncestorContainer; + return contains(this.div, node) + }; + + ContentEditableInput.prototype.focus = function () { + if (this.cm.options.readOnly != "nocursor") { + if (!this.selectionInEditor()) + { this.showSelection(this.prepareSelection(), true); } + this.div.focus(); + } + }; + ContentEditableInput.prototype.blur = function () { this.div.blur(); }; + ContentEditableInput.prototype.getField = function () { return this.div }; + + ContentEditableInput.prototype.supportsTouch = function () { return true }; + + ContentEditableInput.prototype.receivedFocus = function () { + var input = this; + if (this.selectionInEditor()) + { this.pollSelection(); } + else + { runInOp(this.cm, function () { return input.cm.curOp.selectionChanged = true; }); } + + function poll() { + if (input.cm.state.focused) { + input.pollSelection(); + input.polling.set(input.cm.options.pollInterval, poll); + } + } + this.polling.set(this.cm.options.pollInterval, poll); + }; + + ContentEditableInput.prototype.selectionChanged = function () { + var sel = this.getSelection(); + return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset || + sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset + }; + + ContentEditableInput.prototype.pollSelection = function () { + if (this.readDOMTimeout != null || this.gracePeriod || !this.selectionChanged()) { return } + var sel = this.getSelection(), cm = this.cm; + // On Android Chrome (version 56, at least), backspacing into an + // uneditable block element will put the cursor in that element, + // and then, because it's not editable, hide the virtual keyboard. + // Because Android doesn't allow us to actually detect backspace + // presses in a sane way, this code checks for when that happens + // and simulates a backspace press in this case. + if (android && chrome && this.cm.options.gutters.length && isInGutter(sel.anchorNode)) { + this.cm.triggerOnKeyDown({type: "keydown", keyCode: 8, preventDefault: Math.abs}); + this.blur(); + this.focus(); + return + } + if (this.composing) { return } + this.rememberSelection(); + var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset); + var head = domToPos(cm, sel.focusNode, sel.focusOffset); + if (anchor && head) { runInOp(cm, function () { + setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll); + if (anchor.bad || head.bad) { cm.curOp.selectionChanged = true; } + }); } + }; + + ContentEditableInput.prototype.pollContent = function () { + if (this.readDOMTimeout != null) { + clearTimeout(this.readDOMTimeout); + this.readDOMTimeout = null; + } + + var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary(); + var from = sel.from(), to = sel.to(); + if (from.ch == 0 && from.line > cm.firstLine()) + { from = Pos(from.line - 1, getLine(cm.doc, from.line - 1).length); } + if (to.ch == getLine(cm.doc, to.line).text.length && to.line < cm.lastLine()) + { to = Pos(to.line + 1, 0); } + if (from.line < display.viewFrom || to.line > display.viewTo - 1) { return false } + + var fromIndex, fromLine, fromNode; + if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) { + fromLine = lineNo(display.view[0].line); + fromNode = display.view[0].node; + } else { + fromLine = lineNo(display.view[fromIndex].line); + fromNode = display.view[fromIndex - 1].node.nextSibling; + } + var toIndex = findViewIndex(cm, to.line); + var toLine, toNode; + if (toIndex == display.view.length - 1) { + toLine = display.viewTo - 1; + toNode = display.lineDiv.lastChild; + } else { + toLine = lineNo(display.view[toIndex + 1].line) - 1; + toNode = display.view[toIndex + 1].node.previousSibling; + } + + if (!fromNode) { return false } + var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine)); + var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length)); + while (newText.length > 1 && oldText.length > 1) { + if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine--; } + else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++; } + else { break } + } + + var cutFront = 0, cutEnd = 0; + var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length); + while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront)) + { ++cutFront; } + var newBot = lst(newText), oldBot = lst(oldText); + var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0), + oldBot.length - (oldText.length == 1 ? cutFront : 0)); + while (cutEnd < maxCutEnd && + newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) + { ++cutEnd; } + // Try to move start of change to start of selection if ambiguous + if (newText.length == 1 && oldText.length == 1 && fromLine == from.line) { + while (cutFront && cutFront > from.ch && + newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) { + cutFront--; + cutEnd++; + } + } + + newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd).replace(/^\u200b+/, ""); + newText[0] = newText[0].slice(cutFront).replace(/\u200b+$/, ""); + + var chFrom = Pos(fromLine, cutFront); + var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0); + if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) { + replaceRange(cm.doc, newText, chFrom, chTo, "+input"); + return true + } + }; + + ContentEditableInput.prototype.ensurePolled = function () { + this.forceCompositionEnd(); + }; + ContentEditableInput.prototype.reset = function () { + this.forceCompositionEnd(); + }; + ContentEditableInput.prototype.forceCompositionEnd = function () { + if (!this.composing) { return } + clearTimeout(this.readDOMTimeout); + this.composing = null; + this.updateFromDOM(); + this.div.blur(); + this.div.focus(); + }; + ContentEditableInput.prototype.readFromDOMSoon = function () { + var this$1 = this; + + if (this.readDOMTimeout != null) { return } + this.readDOMTimeout = setTimeout(function () { + this$1.readDOMTimeout = null; + if (this$1.composing) { + if (this$1.composing.done) { this$1.composing = null; } + else { return } + } + this$1.updateFromDOM(); + }, 80); + }; + + ContentEditableInput.prototype.updateFromDOM = function () { + var this$1 = this; + + if (this.cm.isReadOnly() || !this.pollContent()) + { runInOp(this.cm, function () { return regChange(this$1.cm); }); } + }; + + ContentEditableInput.prototype.setUneditable = function (node) { + node.contentEditable = "false"; + }; + + ContentEditableInput.prototype.onKeyPress = function (e) { + if (e.charCode == 0 || this.composing) { return } + e.preventDefault(); + if (!this.cm.isReadOnly()) + { operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0); } + }; + + ContentEditableInput.prototype.readOnlyChanged = function (val) { + this.div.contentEditable = String(val != "nocursor"); + }; + + ContentEditableInput.prototype.onContextMenu = function () {}; + ContentEditableInput.prototype.resetPosition = function () {}; + + ContentEditableInput.prototype.needsContentAttribute = true; + + function posToDOM(cm, pos) { + var view = findViewForLine(cm, pos.line); + if (!view || view.hidden) { return null } + var line = getLine(cm.doc, pos.line); + var info = mapFromLineView(view, line, pos.line); + + var order = getOrder(line, cm.doc.direction), side = "left"; + if (order) { + var partPos = getBidiPartAt(order, pos.ch); + side = partPos % 2 ? "right" : "left"; + } + var result = nodeAndOffsetInLineMap(info.map, pos.ch, side); + result.offset = result.collapse == "right" ? result.end : result.start; + return result + } + + function isInGutter(node) { + for (var scan = node; scan; scan = scan.parentNode) + { if (/CodeMirror-gutter-wrapper/.test(scan.className)) { return true } } + return false + } + + function badPos(pos, bad) { if (bad) { pos.bad = true; } return pos } + + function domTextBetween(cm, from, to, fromLine, toLine) { + var text = "", closing = false, lineSep = cm.doc.lineSeparator(), extraLinebreak = false; + function recognizeMarker(id) { return function (marker) { return marker.id == id; } } + function close() { + if (closing) { + text += lineSep; + if (extraLinebreak) { text += lineSep; } + closing = extraLinebreak = false; + } + } + function addText(str) { + if (str) { + close(); + text += str; + } + } + function walk(node) { + if (node.nodeType == 1) { + var cmText = node.getAttribute("cm-text"); + if (cmText) { + addText(cmText); + return + } + var markerID = node.getAttribute("cm-marker"), range$$1; + if (markerID) { + var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID)); + if (found.length && (range$$1 = found[0].find(0))) + { addText(getBetween(cm.doc, range$$1.from, range$$1.to).join(lineSep)); } + return + } + if (node.getAttribute("contenteditable") == "false") { return } + var isBlock = /^(pre|div|p|li|table|br)$/i.test(node.nodeName); + if (!/^br$/i.test(node.nodeName) && node.textContent.length == 0) { return } + + if (isBlock) { close(); } + for (var i = 0; i < node.childNodes.length; i++) + { walk(node.childNodes[i]); } + + if (/^(pre|p)$/i.test(node.nodeName)) { extraLinebreak = true; } + if (isBlock) { closing = true; } + } else if (node.nodeType == 3) { + addText(node.nodeValue.replace(/\u200b/g, "").replace(/\u00a0/g, " ")); + } + } + for (;;) { + walk(from); + if (from == to) { break } + from = from.nextSibling; + extraLinebreak = false; + } + return text + } + + function domToPos(cm, node, offset) { + var lineNode; + if (node == cm.display.lineDiv) { + lineNode = cm.display.lineDiv.childNodes[offset]; + if (!lineNode) { return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true) } + node = null; offset = 0; + } else { + for (lineNode = node;; lineNode = lineNode.parentNode) { + if (!lineNode || lineNode == cm.display.lineDiv) { return null } + if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) { break } + } + } + for (var i = 0; i < cm.display.view.length; i++) { + var lineView = cm.display.view[i]; + if (lineView.node == lineNode) + { return locateNodeInLineView(lineView, node, offset) } + } + } + + function locateNodeInLineView(lineView, node, offset) { + var wrapper = lineView.text.firstChild, bad = false; + if (!node || !contains(wrapper, node)) { return badPos(Pos(lineNo(lineView.line), 0), true) } + if (node == wrapper) { + bad = true; + node = wrapper.childNodes[offset]; + offset = 0; + if (!node) { + var line = lineView.rest ? lst(lineView.rest) : lineView.line; + return badPos(Pos(lineNo(line), line.text.length), bad) + } + } + + var textNode = node.nodeType == 3 ? node : null, topNode = node; + if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) { + textNode = node.firstChild; + if (offset) { offset = textNode.nodeValue.length; } + } + while (topNode.parentNode != wrapper) { topNode = topNode.parentNode; } + var measure = lineView.measure, maps = measure.maps; + + function find(textNode, topNode, offset) { + for (var i = -1; i < (maps ? maps.length : 0); i++) { + var map$$1 = i < 0 ? measure.map : maps[i]; + for (var j = 0; j < map$$1.length; j += 3) { + var curNode = map$$1[j + 2]; + if (curNode == textNode || curNode == topNode) { + var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]); + var ch = map$$1[j] + offset; + if (offset < 0 || curNode != textNode) { ch = map$$1[j + (offset ? 1 : 0)]; } + return Pos(line, ch) + } + } + } + } + var found = find(textNode, topNode, offset); + if (found) { return badPos(found, bad) } + + // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems + for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) { + found = find(after, after.firstChild, 0); + if (found) + { return badPos(Pos(found.line, found.ch - dist), bad) } + else + { dist += after.textContent.length; } + } + for (var before = topNode.previousSibling, dist$1 = offset; before; before = before.previousSibling) { + found = find(before, before.firstChild, -1); + if (found) + { return badPos(Pos(found.line, found.ch + dist$1), bad) } + else + { dist$1 += before.textContent.length; } + } + } + + // TEXTAREA INPUT STYLE + + var TextareaInput = function(cm) { + this.cm = cm; + // See input.poll and input.reset + this.prevInput = ""; + + // Flag that indicates whether we expect input to appear real soon + // now (after some event like 'keypress' or 'input') and are + // polling intensively. + this.pollingFast = false; + // Self-resetting timeout for the poller + this.polling = new Delayed(); + // Used to work around IE issue with selection being forgotten when focus moves away from textarea + this.hasSelection = false; + this.composing = null; + }; + + TextareaInput.prototype.init = function (display) { + var this$1 = this; + + var input = this, cm = this.cm; + this.createField(display); + var te = this.textarea; + + display.wrapper.insertBefore(this.wrapper, display.wrapper.firstChild); + + // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore) + if (ios) { te.style.width = "0px"; } + + on(te, "input", function () { + if (ie && ie_version >= 9 && this$1.hasSelection) { this$1.hasSelection = null; } + input.poll(); + }); + + on(te, "paste", function (e) { + if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return } + + cm.state.pasteIncoming = +new Date; + input.fastPoll(); + }); + + function prepareCopyCut(e) { + if (signalDOMEvent(cm, e)) { return } + if (cm.somethingSelected()) { + setLastCopied({lineWise: false, text: cm.getSelections()}); + } else if (!cm.options.lineWiseCopyCut) { + return + } else { + var ranges = copyableRanges(cm); + setLastCopied({lineWise: true, text: ranges.text}); + if (e.type == "cut") { + cm.setSelections(ranges.ranges, null, sel_dontScroll); + } else { + input.prevInput = ""; + te.value = ranges.text.join("\n"); + selectInput(te); + } + } + if (e.type == "cut") { cm.state.cutIncoming = +new Date; } + } + on(te, "cut", prepareCopyCut); + on(te, "copy", prepareCopyCut); + + on(display.scroller, "paste", function (e) { + if (eventInWidget(display, e) || signalDOMEvent(cm, e)) { return } + if (!te.dispatchEvent) { + cm.state.pasteIncoming = +new Date; + input.focus(); + return + } + + // Pass the `paste` event to the textarea so it's handled by its event listener. + var event = new Event("paste"); + event.clipboardData = e.clipboardData; + te.dispatchEvent(event); + }); + + // Prevent normal selection in the editor (we handle our own) + on(display.lineSpace, "selectstart", function (e) { + if (!eventInWidget(display, e)) { e_preventDefault(e); } + }); + + on(te, "compositionstart", function () { + var start = cm.getCursor("from"); + if (input.composing) { input.composing.range.clear(); } + input.composing = { + start: start, + range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-composing"}) + }; + }); + on(te, "compositionend", function () { + if (input.composing) { + input.poll(); + input.composing.range.clear(); + input.composing = null; + } + }); + }; + + TextareaInput.prototype.createField = function (_display) { + // Wraps and hides input textarea + this.wrapper = hiddenTextarea(); + // The semihidden textarea that is focused when the editor is + // focused, and receives input. + this.textarea = this.wrapper.firstChild; + }; + + TextareaInput.prototype.prepareSelection = function () { + // Redraw the selection and/or cursor + var cm = this.cm, display = cm.display, doc = cm.doc; + var result = prepareSelection(cm); + + // Move the hidden textarea near the cursor to prevent scrolling artifacts + if (cm.options.moveInputWithCursor) { + var headPos = cursorCoords(cm, doc.sel.primary().head, "div"); + var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect(); + result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, + headPos.top + lineOff.top - wrapOff.top)); + result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, + headPos.left + lineOff.left - wrapOff.left)); + } + + return result + }; + + TextareaInput.prototype.showSelection = function (drawn) { + var cm = this.cm, display = cm.display; + removeChildrenAndAdd(display.cursorDiv, drawn.cursors); + removeChildrenAndAdd(display.selectionDiv, drawn.selection); + if (drawn.teTop != null) { + this.wrapper.style.top = drawn.teTop + "px"; + this.wrapper.style.left = drawn.teLeft + "px"; + } + }; + + // Reset the input to correspond to the selection (or to be empty, + // when not typing and nothing is selected) + TextareaInput.prototype.reset = function (typing) { + if (this.contextMenuPending || this.composing) { return } + var cm = this.cm; + if (cm.somethingSelected()) { + this.prevInput = ""; + var content = cm.getSelection(); + this.textarea.value = content; + if (cm.state.focused) { selectInput(this.textarea); } + if (ie && ie_version >= 9) { this.hasSelection = content; } + } else if (!typing) { + this.prevInput = this.textarea.value = ""; + if (ie && ie_version >= 9) { this.hasSelection = null; } + } + }; + + TextareaInput.prototype.getField = function () { return this.textarea }; + + TextareaInput.prototype.supportsTouch = function () { return false }; + + TextareaInput.prototype.focus = function () { + if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.textarea)) { + try { this.textarea.focus(); } + catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM + } + }; + + TextareaInput.prototype.blur = function () { this.textarea.blur(); }; + + TextareaInput.prototype.resetPosition = function () { + this.wrapper.style.top = this.wrapper.style.left = 0; + }; + + TextareaInput.prototype.receivedFocus = function () { this.slowPoll(); }; + + // Poll for input changes, using the normal rate of polling. This + // runs as long as the editor is focused. + TextareaInput.prototype.slowPoll = function () { + var this$1 = this; + + if (this.pollingFast) { return } + this.polling.set(this.cm.options.pollInterval, function () { + this$1.poll(); + if (this$1.cm.state.focused) { this$1.slowPoll(); } + }); + }; + + // When an event has just come in that is likely to add or change + // something in the input textarea, we poll faster, to ensure that + // the change appears on the screen quickly. + TextareaInput.prototype.fastPoll = function () { + var missed = false, input = this; + input.pollingFast = true; + function p() { + var changed = input.poll(); + if (!changed && !missed) {missed = true; input.polling.set(60, p);} + else {input.pollingFast = false; input.slowPoll();} + } + input.polling.set(20, p); + }; + + // Read input from the textarea, and update the document to match. + // When something is selected, it is present in the textarea, and + // selected (unless it is huge, in which case a placeholder is + // used). When nothing is selected, the cursor sits after previously + // seen text (can be empty), which is stored in prevInput (we must + // not reset the textarea when typing, because that breaks IME). + TextareaInput.prototype.poll = function () { + var this$1 = this; + + var cm = this.cm, input = this.textarea, prevInput = this.prevInput; + // Since this is called a *lot*, try to bail out as cheaply as + // possible when it is clear that nothing happened. hasSelection + // will be the case when there is a lot of text in the textarea, + // in which case reading its value would be expensive. + if (this.contextMenuPending || !cm.state.focused || + (hasSelection(input) && !prevInput && !this.composing) || + cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq) + { return false } + + var text = input.value; + // If nothing changed, bail. + if (text == prevInput && !cm.somethingSelected()) { return false } + // Work around nonsensical selection resetting in IE9/10, and + // inexplicable appearance of private area unicode characters on + // some key combos in Mac (#2689). + if (ie && ie_version >= 9 && this.hasSelection === text || + mac && /[\uf700-\uf7ff]/.test(text)) { + cm.display.input.reset(); + return false + } + + if (cm.doc.sel == cm.display.selForContextMenu) { + var first = text.charCodeAt(0); + if (first == 0x200b && !prevInput) { prevInput = "\u200b"; } + if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo") } + } + // Find the part of the input that is actually new + var same = 0, l = Math.min(prevInput.length, text.length); + while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) { ++same; } + + runInOp(cm, function () { + applyTextInput(cm, text.slice(same), prevInput.length - same, + null, this$1.composing ? "*compose" : null); + + // Don't leave long text in the textarea, since it makes further polling slow + if (text.length > 1000 || text.indexOf("\n") > -1) { input.value = this$1.prevInput = ""; } + else { this$1.prevInput = text; } + + if (this$1.composing) { + this$1.composing.range.clear(); + this$1.composing.range = cm.markText(this$1.composing.start, cm.getCursor("to"), + {className: "CodeMirror-composing"}); + } + }); + return true + }; + + TextareaInput.prototype.ensurePolled = function () { + if (this.pollingFast && this.poll()) { this.pollingFast = false; } + }; + + TextareaInput.prototype.onKeyPress = function () { + if (ie && ie_version >= 9) { this.hasSelection = null; } + this.fastPoll(); + }; + + TextareaInput.prototype.onContextMenu = function (e) { + var input = this, cm = input.cm, display = cm.display, te = input.textarea; + if (input.contextMenuPending) { input.contextMenuPending(); } + var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop; + if (!pos || presto) { return } // Opera is difficult. + + // Reset the current text selection only if the click is done outside of the selection + // and 'resetSelectionOnContextMenu' option is true. + var reset = cm.options.resetSelectionOnContextMenu; + if (reset && cm.doc.sel.contains(pos) == -1) + { operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll); } + + var oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText; + var wrapperBox = input.wrapper.offsetParent.getBoundingClientRect(); + input.wrapper.style.cssText = "position: static"; + te.style.cssText = "position: absolute; width: 30px; height: 30px;\n top: " + (e.clientY - wrapperBox.top - 5) + "px; left: " + (e.clientX - wrapperBox.left - 5) + "px;\n z-index: 1000; background: " + (ie ? "rgba(255, 255, 255, .05)" : "transparent") + ";\n outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; + var oldScrollY; + if (webkit) { oldScrollY = window.scrollY; } // Work around Chrome issue (#2712) + display.input.focus(); + if (webkit) { window.scrollTo(null, oldScrollY); } + display.input.reset(); + // Adds "Select all" to context menu in FF + if (!cm.somethingSelected()) { te.value = input.prevInput = " "; } + input.contextMenuPending = rehide; + display.selForContextMenu = cm.doc.sel; + clearTimeout(display.detectingSelectAll); + + // Select-all will be greyed out if there's nothing to select, so + // this adds a zero-width space so that we can later check whether + // it got selected. + function prepareSelectAllHack() { + if (te.selectionStart != null) { + var selected = cm.somethingSelected(); + var extval = "\u200b" + (selected ? te.value : ""); + te.value = "\u21da"; // Used to catch context-menu undo + te.value = extval; + input.prevInput = selected ? "" : "\u200b"; + te.selectionStart = 1; te.selectionEnd = extval.length; + // Re-set this, in case some other handler touched the + // selection in the meantime. + display.selForContextMenu = cm.doc.sel; + } + } + function rehide() { + if (input.contextMenuPending != rehide) { return } + input.contextMenuPending = false; + input.wrapper.style.cssText = oldWrapperCSS; + te.style.cssText = oldCSS; + if (ie && ie_version < 9) { display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos); } + + // Try to detect the user choosing select-all + if (te.selectionStart != null) { + if (!ie || (ie && ie_version < 9)) { prepareSelectAllHack(); } + var i = 0, poll = function () { + if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 && + te.selectionEnd > 0 && input.prevInput == "\u200b") { + operation(cm, selectAll)(cm); + } else if (i++ < 10) { + display.detectingSelectAll = setTimeout(poll, 500); + } else { + display.selForContextMenu = null; + display.input.reset(); + } + }; + display.detectingSelectAll = setTimeout(poll, 200); + } + } + + if (ie && ie_version >= 9) { prepareSelectAllHack(); } + if (captureRightClick) { + e_stop(e); + var mouseup = function () { + off(window, "mouseup", mouseup); + setTimeout(rehide, 20); + }; + on(window, "mouseup", mouseup); + } else { + setTimeout(rehide, 50); + } + }; + + TextareaInput.prototype.readOnlyChanged = function (val) { + if (!val) { this.reset(); } + this.textarea.disabled = val == "nocursor"; + }; + + TextareaInput.prototype.setUneditable = function () {}; + + TextareaInput.prototype.needsContentAttribute = false; + + function fromTextArea(textarea, options) { + options = options ? copyObj(options) : {}; + options.value = textarea.value; + if (!options.tabindex && textarea.tabIndex) + { options.tabindex = textarea.tabIndex; } + if (!options.placeholder && textarea.placeholder) + { options.placeholder = textarea.placeholder; } + // Set autofocus to true if this textarea is focused, or if it has + // autofocus and no other element is focused. + if (options.autofocus == null) { + var hasFocus = activeElt(); + options.autofocus = hasFocus == textarea || + textarea.getAttribute("autofocus") != null && hasFocus == document.body; + } + + function save() {textarea.value = cm.getValue();} + + var realSubmit; + if (textarea.form) { + on(textarea.form, "submit", save); + // Deplorable hack to make the submit method do the right thing. + if (!options.leaveSubmitMethodAlone) { + var form = textarea.form; + realSubmit = form.submit; + try { + var wrappedSubmit = form.submit = function () { + save(); + form.submit = realSubmit; + form.submit(); + form.submit = wrappedSubmit; + }; + } catch(e) {} + } + } + + options.finishInit = function (cm) { + cm.save = save; + cm.getTextArea = function () { return textarea; }; + cm.toTextArea = function () { + cm.toTextArea = isNaN; // Prevent this from being ran twice + save(); + textarea.parentNode.removeChild(cm.getWrapperElement()); + textarea.style.display = ""; + if (textarea.form) { + off(textarea.form, "submit", save); + if (typeof textarea.form.submit == "function") + { textarea.form.submit = realSubmit; } + } + }; + }; + + textarea.style.display = "none"; + var cm = CodeMirror(function (node) { return textarea.parentNode.insertBefore(node, textarea.nextSibling); }, + options); + return cm + } + + function addLegacyProps(CodeMirror) { + CodeMirror.off = off; + CodeMirror.on = on; + CodeMirror.wheelEventPixels = wheelEventPixels; + CodeMirror.Doc = Doc; + CodeMirror.splitLines = splitLinesAuto; + CodeMirror.countColumn = countColumn; + CodeMirror.findColumn = findColumn; + CodeMirror.isWordChar = isWordCharBasic; + CodeMirror.Pass = Pass; + CodeMirror.signal = signal; + CodeMirror.Line = Line; + CodeMirror.changeEnd = changeEnd; + CodeMirror.scrollbarModel = scrollbarModel; + CodeMirror.Pos = Pos; + CodeMirror.cmpPos = cmp; + CodeMirror.modes = modes; + CodeMirror.mimeModes = mimeModes; + CodeMirror.resolveMode = resolveMode; + CodeMirror.getMode = getMode; + CodeMirror.modeExtensions = modeExtensions; + CodeMirror.extendMode = extendMode; + CodeMirror.copyState = copyState; + CodeMirror.startState = startState; + CodeMirror.innerMode = innerMode; + CodeMirror.commands = commands; + CodeMirror.keyMap = keyMap; + CodeMirror.keyName = keyName; + CodeMirror.isModifierKey = isModifierKey; + CodeMirror.lookupKey = lookupKey; + CodeMirror.normalizeKeyMap = normalizeKeyMap; + CodeMirror.StringStream = StringStream; + CodeMirror.SharedTextMarker = SharedTextMarker; + CodeMirror.TextMarker = TextMarker; + CodeMirror.LineWidget = LineWidget; + CodeMirror.e_preventDefault = e_preventDefault; + CodeMirror.e_stopPropagation = e_stopPropagation; + CodeMirror.e_stop = e_stop; + CodeMirror.addClass = addClass; + CodeMirror.contains = contains; + CodeMirror.rmClass = rmClass; + CodeMirror.keyNames = keyNames; + } + + // EDITOR CONSTRUCTOR + + defineOptions(CodeMirror); + + addEditorMethods(CodeMirror); + + // Set up methods on CodeMirror's prototype to redirect to the editor's document. + var dontDelegate = "iter insert remove copy getEditor constructor".split(" "); + for (var prop in Doc.prototype) { if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0) + { CodeMirror.prototype[prop] = (function(method) { + return function() {return method.apply(this.doc, arguments)} + })(Doc.prototype[prop]); } } + + eventMixin(Doc); + CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput}; + + // Extra arguments are stored as the mode's dependencies, which is + // used by (legacy) mechanisms like loadmode.js to automatically + // load a mode. (Preferred mechanism is the require/define calls.) + CodeMirror.defineMode = function(name/*, mode, …*/) { + if (!CodeMirror.defaults.mode && name != "null") { CodeMirror.defaults.mode = name; } + defineMode.apply(this, arguments); + }; + + CodeMirror.defineMIME = defineMIME; + + // Minimal default mode. + CodeMirror.defineMode("null", function () { return ({token: function (stream) { return stream.skipToEnd(); }}); }); + CodeMirror.defineMIME("text/plain", "null"); + + // EXTENSIONS + + CodeMirror.defineExtension = function (name, func) { + CodeMirror.prototype[name] = func; + }; + CodeMirror.defineDocExtension = function (name, func) { + Doc.prototype[name] = func; + }; + + CodeMirror.fromTextArea = fromTextArea; + + addLegacyProps(CodeMirror); + + CodeMirror.version = "5.45.0"; + + return CodeMirror; + +}))); + + +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +;(function(mod) { + if (typeof exports == "object" && typeof module == "object") + // CommonJS + mod(require("../../lib/codemirror")) + else if (typeof define == "function" && define.amd) + // AMD + define(["../../lib/codemirror"], mod) + // Plain browser env + else mod(CodeMirror) +})(function(CodeMirror) { + "use strict" + + var HINT_ELEMENT_CLASS = "CodeMirror-hint" + var ACTIVE_HINT_ELEMENT_CLASS = "CodeMirror-hint-active" + + // This is the old interface, kept around for now to stay + // backwards-compatible. + CodeMirror.showHint = function(cm, getHints, options) { + if (!getHints) return cm.showHint(options) + if (options && options.async) getHints.async = true + var newOpts = { hint: getHints } + if (options) for (var prop in options) newOpts[prop] = options[prop] + return cm.showHint(newOpts) + } + + CodeMirror.defineExtension("showHint", function(options) { + options = parseOptions(this, this.getCursor("start"), options) + var selections = this.listSelections() + if (selections.length > 1) return + // By default, don't allow completion when something is selected. + // A hint function can have a `supportsSelection` property to + // indicate that it can handle selections. + if (this.somethingSelected()) { + if (!options.hint.supportsSelection) return + // Don't try with cross-line selections + for (var i = 0; i < selections.length; i++) if (selections[i].head.line != selections[i].anchor.line) return + } + + if (this.state.completionActive) this.state.completionActive.close() + var completion = (this.state.completionActive = new Completion(this, options)) + if (!completion.options.hint) return + + CodeMirror.signal(this, "startCompletion", this) + completion.update(true) + }) + + CodeMirror.defineExtension("closeHint", function() { + if (this.state.completionActive) this.state.completionActive.close() + }) + + function Completion(cm, options) { + this.cm = cm + this.options = options + this.widget = null + this.debounce = 0 + this.tick = 0 + this.startPos = this.cm.getCursor("start") + this.startLen = this.cm.getLine(this.startPos.line).length - this.cm.getSelection().length + + var self = this + cm.on( + "cursorActivity", + (this.activityFunc = function() { + self.cursorActivity() + }) + ) + } + + var requestAnimationFrame = + window.requestAnimationFrame || + function(fn) { + return setTimeout(fn, 1000 / 60) + } + var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout + + Completion.prototype = { + close: function() { + if (!this.active()) return + this.cm.state.completionActive = null + this.tick = null + this.cm.off("cursorActivity", this.activityFunc) + + if (this.widget && this.data) CodeMirror.signal(this.data, "close") + if (this.widget) this.widget.close() + CodeMirror.signal(this.cm, "endCompletion", this.cm) + }, + + active: function() { + return this.cm.state.completionActive == this + }, + + pick: function(data, i) { + var completion = data.list[i] + if (completion.hint) completion.hint(this.cm, data, completion) + else this.cm.replaceRange(getText(completion), completion.from || data.from, completion.to || data.to, "complete") + CodeMirror.signal(data, "pick", completion) + this.close() + }, + + cursorActivity: function() { + if (this.debounce) { + cancelAnimationFrame(this.debounce) + this.debounce = 0 + } + + var pos = this.cm.getCursor(), + line = this.cm.getLine(pos.line) + if ( + pos.line != this.startPos.line || + line.length - pos.ch != this.startLen - this.startPos.ch || + pos.ch < this.startPos.ch || + this.cm.somethingSelected() || + (!pos.ch || this.options.closeCharacters.test(line.charAt(pos.ch - 1))) + ) { + this.close() + } else { + var self = this + this.debounce = requestAnimationFrame(function() { + self.update() + }) + if (this.widget) this.widget.disable() + } + }, + + update: function(first) { + if (this.tick == null) return + var self = this, + myTick = ++this.tick + fetchHints(this.options.hint, this.cm, this.options, function(data) { + if (self.tick == myTick) self.finishUpdate(data, first) + }) + }, + + finishUpdate: function(data, first) { + if (this.data) CodeMirror.signal(this.data, "update") + + var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle) + if (this.widget) this.widget.close() + + this.data = data + + if (data && data.list.length) { + if (picked && data.list.length == 1) { + this.pick(data, 0) + } else { + this.widget = new Widget(this, data) + CodeMirror.signal(data, "shown") + } + } + } + } + + function parseOptions(cm, pos, options) { + var editor = cm.options.hintOptions + var out = {} + for (var prop in defaultOptions) out[prop] = defaultOptions[prop] + if (editor) for (var prop in editor) if (editor[prop] !== undefined) out[prop] = editor[prop] + if (options) for (var prop in options) if (options[prop] !== undefined) out[prop] = options[prop] + if (out.hint.resolve) out.hint = out.hint.resolve(cm, pos) + return out + } + + function getText(completion) { + if (typeof completion == "string") return completion + else return completion.text + } + + function buildKeyMap(completion, handle) { + var baseMap = { + Up: function() { + handle.moveFocus(-1) + }, + Down: function() { + handle.moveFocus(1) + }, + PageUp: function() { + handle.moveFocus(-handle.menuSize() + 1, true) + }, + PageDown: function() { + handle.moveFocus(handle.menuSize() - 1, true) + }, + Home: function() { + handle.setFocus(0) + }, + End: function() { + handle.setFocus(handle.length - 1) + }, + Enter: handle.pick, + Tab: handle.pick, + Esc: handle.close + } + + var mac = /Mac/.test(navigator.platform) + + if (mac) { + baseMap["Ctrl-P"] = function() { + handle.moveFocus(-1) + } + baseMap["Ctrl-N"] = function() { + handle.moveFocus(1) + } + } + + var custom = completion.options.customKeys + var ourMap = custom ? {} : baseMap + function addBinding(key, val) { + var bound + if (typeof val != "string") + bound = function(cm) { + return val(cm, handle) + } + // This mechanism is deprecated + else if (baseMap.hasOwnProperty(val)) bound = baseMap[val] + else bound = val + ourMap[key] = bound + } + if (custom) for (var key in custom) if (custom.hasOwnProperty(key)) addBinding(key, custom[key]) + var extra = completion.options.extraKeys + if (extra) for (var key in extra) if (extra.hasOwnProperty(key)) addBinding(key, extra[key]) + return ourMap + } + + function getHintElement(hintsElement, el) { + while (el && el != hintsElement) { + if (el.nodeName.toUpperCase() === "LI" && el.parentNode == hintsElement) return el + el = el.parentNode + } + } + + function Widget(completion, data) { + this.completion = completion + this.data = data + this.picked = false + var widget = this, + cm = completion.cm + var ownerDocument = cm.getInputField().ownerDocument + var parentWindow = ownerDocument.defaultView || ownerDocument.parentWindow + + var hints = (this.hints = ownerDocument.createElement("ul")) + var theme = completion.cm.options.theme + hints.className = "CodeMirror-hints " + theme + this.selectedHint = data.selectedHint || 0 + + var completions = data.list + for (var i = 0; i < completions.length; ++i) { + var elt = hints.appendChild(ownerDocument.createElement("li")), + cur = completions[i] + var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? "" : " " + ACTIVE_HINT_ELEMENT_CLASS) + if (cur.className != null) className = cur.className + " " + className + elt.className = className + if (cur.render) cur.render(elt, data, cur) + else elt.appendChild(ownerDocument.createTextNode(cur.displayText || getText(cur))) + elt.hintId = i + } + + var pos = cm.cursorCoords(completion.options.alignWithWord ? data.from : null) + var left = pos.left, + top = pos.bottom, + below = true + hints.style.left = left + "px" + hints.style.top = top + "px" + // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor. + var winW = + parentWindow.innerWidth || Math.max(ownerDocument.body.offsetWidth, ownerDocument.documentElement.offsetWidth) + var winH = + parentWindow.innerHeight || Math.max(ownerDocument.body.offsetHeight, ownerDocument.documentElement.offsetHeight) + ;(completion.options.container || ownerDocument.body).appendChild(hints) + var box = hints.getBoundingClientRect(), + overlapY = box.bottom - winH + var scrolls = hints.scrollHeight > hints.clientHeight + 1 + var startScroll = cm.getScrollInfo() + + if (overlapY > 0) { + var height = box.bottom - box.top, + curTop = pos.top - (pos.bottom - box.top) + if (curTop - height > 0) { + // Fits above cursor + hints.style.top = (top = pos.top - height) + "px" + below = false + } else if (height > winH) { + hints.style.height = winH - 5 + "px" + hints.style.top = (top = pos.bottom - box.top) + "px" + var cursor = cm.getCursor() + if (data.from.ch != cursor.ch) { + pos = cm.cursorCoords(cursor) + hints.style.left = (left = pos.left) + "px" + box = hints.getBoundingClientRect() + } + } + } + var overlapX = box.right - winW + if (overlapX > 0) { + if (box.right - box.left > winW) { + hints.style.width = winW - 5 + "px" + overlapX -= box.right - box.left - winW + } + hints.style.left = (left = pos.left - overlapX) + "px" + } + if (scrolls) + for (var node = hints.firstChild; node; node = node.nextSibling) + node.style.paddingRight = cm.display.nativeBarWidth + "px" + + cm.addKeyMap( + (this.keyMap = buildKeyMap(completion, { + moveFocus: function(n, avoidWrap) { + widget.changeActive(widget.selectedHint + n, avoidWrap) + }, + setFocus: function(n) { + widget.changeActive(n) + }, + menuSize: function() { + return widget.screenAmount() + }, + length: completions.length, + close: function() { + completion.close() + }, + pick: function() { + widget.pick() + }, + data: data + })) + ) + + if (completion.options.closeOnUnfocus) { + var closingOnBlur + cm.on( + "blur", + (this.onBlur = function() { + closingOnBlur = setTimeout(function() { + completion.close() + }, 100) + }) + ) + cm.on( + "focus", + (this.onFocus = function() { + clearTimeout(closingOnBlur) + }) + ) + } + + cm.on( + "scroll", + (this.onScroll = function() { + var curScroll = cm.getScrollInfo(), + editor = cm.getWrapperElement().getBoundingClientRect() + var newTop = top + startScroll.top - curScroll.top + var point = + newTop - (parentWindow.pageYOffset || (ownerDocument.documentElement || ownerDocument.body).scrollTop) + if (!below) point += hints.offsetHeight + if (point <= editor.top || point >= editor.bottom) return completion.close() + hints.style.top = newTop + "px" + hints.style.left = left + startScroll.left - curScroll.left + "px" + }) + ) + + CodeMirror.on(hints, "dblclick", function(e) { + var t = getHintElement(hints, e.target || e.srcElement) + if (t && t.hintId != null) { + widget.changeActive(t.hintId) + widget.pick() + } + }) + + CodeMirror.on(hints, "click", function(e) { + var t = getHintElement(hints, e.target || e.srcElement) + if (t && t.hintId != null) { + widget.changeActive(t.hintId) + if (completion.options.completeOnSingleClick) widget.pick() + } + }) + + CodeMirror.on(hints, "mousedown", function() { + setTimeout(function() { + cm.focus() + }, 20) + }) + + CodeMirror.signal(data, "select", completions[this.selectedHint], hints.childNodes[this.selectedHint]) + return true + } + + Widget.prototype = { + close: function() { + if (this.completion.widget != this) return + this.completion.widget = null + this.hints.parentNode.removeChild(this.hints) + this.completion.cm.removeKeyMap(this.keyMap) + + var cm = this.completion.cm + if (this.completion.options.closeOnUnfocus) { + cm.off("blur", this.onBlur) + cm.off("focus", this.onFocus) + } + cm.off("scroll", this.onScroll) + }, + + disable: function() { + this.completion.cm.removeKeyMap(this.keyMap) + var widget = this + this.keyMap = { + Enter: function() { + widget.picked = true + } + } + this.completion.cm.addKeyMap(this.keyMap) + }, + + pick: function() { + this.completion.pick(this.data, this.selectedHint) + }, + + changeActive: function(i, avoidWrap) { + if (i >= this.data.list.length) i = avoidWrap ? this.data.list.length - 1 : 0 + else if (i < 0) i = avoidWrap ? 0 : this.data.list.length - 1 + if (this.selectedHint == i) return + var node = this.hints.childNodes[this.selectedHint] + if (node) node.className = node.className.replace(" " + ACTIVE_HINT_ELEMENT_CLASS, "") + node = this.hints.childNodes[(this.selectedHint = i)] + node.className += " " + ACTIVE_HINT_ELEMENT_CLASS + if (node.offsetTop < this.hints.scrollTop) this.hints.scrollTop = node.offsetTop - 3 + else if (node.offsetTop + node.offsetHeight > this.hints.scrollTop + this.hints.clientHeight) + this.hints.scrollTop = node.offsetTop + node.offsetHeight - this.hints.clientHeight + 3 + CodeMirror.signal(this.data, "select", this.data.list[this.selectedHint], node) + }, + + screenAmount: function() { + return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1 + } + } + + function applicableHelpers(cm, helpers) { + if (!cm.somethingSelected()) return helpers + var result = [] + for (var i = 0; i < helpers.length; i++) if (helpers[i].supportsSelection) result.push(helpers[i]) + return result + } + + function fetchHints(hint, cm, options, callback) { + if (hint.async) { + hint(cm, callback, options) + } else { + var result = hint(cm, options) + if (result && result.then) result.then(callback) + else callback(result) + } + } + + function resolveAutoHints(cm, pos) { + var helpers = cm.getHelpers(pos, "hint"), + words + if (helpers.length) { + var resolved = function(cm, callback, options) { + var app = applicableHelpers(cm, helpers) + function run(i) { + if (i == app.length) return callback(null) + fetchHints(app[i], cm, options, function(result) { + if (result && result.list.length > 0) callback(result) + else run(i + 1) + }) + } + run(0) + } + resolved.async = true + resolved.supportsSelection = true + return resolved + } else if ((words = cm.getHelper(cm.getCursor(), "hintWords"))) { + return function(cm) { + return CodeMirror.hint.fromList(cm, { words: words }) + } + } else if (CodeMirror.hint.anyword) { + return function(cm, options) { + return CodeMirror.hint.anyword(cm, options) + } + } else { + return function() {} + } + } + + CodeMirror.registerHelper("hint", "auto", { + resolve: resolveAutoHints + }) + + CodeMirror.registerHelper("hint", "fromList", function(cm, options) { + var cur = cm.getCursor(), + token = cm.getTokenAt(cur) + var term, + from = CodeMirror.Pos(cur.line, token.start), + to = cur + if (token.start < cur.ch && /\w/.test(token.string.charAt(cur.ch - token.start - 1))) { + term = token.string.substr(0, cur.ch - token.start) + } else { + term = "" + from = cur + } + var found = [] + for (var i = 0; i < options.words.length; i++) { + var word = options.words[i] + if (word.slice(0, term.length) == term) found.push(word) + } + + if (found.length) return { list: found, from: from, to: to } + }) + + CodeMirror.commands.autocomplete = CodeMirror.showHint + + var defaultOptions = { + hint: CodeMirror.hint.auto, + completeSingle: true, + alignWithWord: true, + closeCharacters: /[\s()\[\]{};:>,]/, + closeOnUnfocus: true, + completeOnSingleClick: true, + container: null, + customKeys: null, + extraKeys: null + } + + CodeMirror.defineOption("hintOptions", null) +}) + + +class Timer { + constructor() { + this._tickTime = Date.now() - (Utils.isNodeJs() ? 1000 * process.uptime() : 0) + this._firstTickTime = this._tickTime + } + tick(msg) { + const elapsed = Date.now() - this._tickTime + if (msg) console.log(`${elapsed}ms ${msg}`) + this._tickTime = Date.now() + return elapsed + } + getTotalElapsedTime() { + return Date.now() - this._firstTickTime + } +} +class Utils { + static getFileExtension(filepath = "") { + const match = filepath.match(/\.([^\.]+)$/) + return (match && match[1]) || "" + } + static ensureFolderEndsInSlash(folder) { + return folder.replace(/\/$/, "") + "/" + } + static runCommand(instance, command = "", param = undefined) { + const run = name => { + console.log(`Running ${name}:`) + instance[name](param) + } + if (instance[command + "Command"]) return run(command + "Command") + // Get commands from both the child and parent classes + const classes = [Object.getPrototypeOf(instance), Object.getPrototypeOf(Object.getPrototypeOf(instance))] + const allCommands = classes.map(classInstance => Object.getOwnPropertyNames(classInstance).filter(word => word.endsWith("Command"))).flat() + allCommands.sort() + const commandAsNumber = parseInt(command) - 1 + if (command.match(/^\d+$/) && allCommands[commandAsNumber]) return run(allCommands[commandAsNumber]) + console.log(`\n❌ No command provided. Available commands:\n\n` + allCommands.map((name, index) => `${index + 1}. ${name.replace("Command", "")}`).join("\n") + "\n") + } + static removeReturnChars(str = "") { + return str.replace(/\r/g, "") + } + static isAbsoluteUrl(url) { + return url.startsWith("https://") || url.startsWith("http://") + } + static removeEmptyLines(str = "") { + return str.replace(/\n\n+/g, "\n") + } + static shiftRight(str = "", numSpaces = 1) { + let spaces = " ".repeat(numSpaces) + return str.replace(/\n/g, `\n${spaces}`) + } + static getLinks(str = "") { + const _re = new RegExp("(^|[ \t\r\n])((ftp|http|https):(([A-Za-z0-9$_.+!*(),;/?:@&~=-])|%[A-Fa-f0-9]{2}){2,}(#([a-zA-Z0-9][a-zA-Z0-9$_.+!*(),;/?:@&~=%-]*))?([A-Za-z0-9$_+!*();/?:~-]))", "g") + return str.match(_re) || [] + } + // Only allow text content and inline styling. Don't allow HTML tags or any nested scroll tags or escape characters. + static escapeScrollAndHtml(content = "") { + return content.replace(/ word.includes(delimiter)) + if (hit) throw `Delimiter "${delimiter}" found in hit` + } + // https://github.com/rigoneri/indefinite-article.js/blob/master/indefinite-article.js + static getIndefiniteArticle(phrase) { + // Getting the first word + const match = /\w+/.exec(phrase) + let word + if (match) word = match[0] + else return "an" + var l_word = word.toLowerCase() + // Specific start of words that should be preceded by 'an' + var alt_cases = ["honest", "hour", "hono"] + for (var i in alt_cases) { + if (l_word.indexOf(alt_cases[i]) == 0) return "an" + } + // Single letter word which should be preceded by 'an' + if (l_word.length == 1) { + if ("aedhilmnorsx".indexOf(l_word) >= 0) return "an" + else return "a" + } + // Capital words which should likely be preceded by 'an' + if (word.match(/(?!FJO|[HLMNS]Y.|RY[EO]|SQU|(F[LR]?|[HL]|MN?|N|RH?|S[CHKLMNPTVW]?|X(YL)?)[AEIOU])[FHLMNRSX][A-Z]/)) { + return "an" + } + // Special cases where a word that begins with a vowel should be preceded by 'a' + const regexes = [/^e[uw]/, /^onc?e\b/, /^uni([^nmd]|mo)/, /^u[bcfhjkqrst][aeiou]/] + for (var i in regexes) { + if (l_word.match(regexes[i])) return "a" + } + // Special capital words (UK, UN) + if (word.match(/^U[NK][AIEO]/)) { + return "a" + } else if (word == word.toUpperCase()) { + if ("aedhilmnorsx".indexOf(l_word[0]) >= 0) return "an" + else return "a" + } + // Basic method of words that begin with a vowel being preceded by 'an' + if ("aeiou".indexOf(l_word[0]) >= 0) return "an" + // Instances where y follwed by specific letters is preceded by 'an' + if (l_word.match(/^y(b[lor]|cl[ea]|fere|gg|p[ios]|rou|tt)/)) return "an" + return "a" + } + static htmlEscaped(content = "") { + return content.replace(/()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/) + } + static capitalizeFirstLetter(str) { + return str.charAt(0).toUpperCase() + str.slice(1) + } + // generate a random alpha numeric hash: + static getRandomCharacters(length) { + const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + let result = "" + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * characters.length)) + } + return result + } + static isNodeJs() { + return typeof exports !== "undefined" + } + static findProjectRoot(startingDirName, projectName) { + const fs = require("fs") + const getProjectName = dirName => { + if (!dirName) throw new Error(`dirName undefined when attempting to findProjectRoot for project "${projectName}" starting in "${startingDirName}"`) + const parts = dirName.split("/") + const filename = parts.join("/") + "/" + "package.json" + if (fs.existsSync(filename) && JSON.parse(fs.readFileSync(filename, "utf8")).name === projectName) return parts.join("/") + "/" + parts.pop() + return parts + } + let result = getProjectName(startingDirName) + while (typeof result !== "string" && result.length > 0) { + result = getProjectName(result.join("/")) + } + if (result.length === 0) throw new Error(`Project root "${projectName}" in folder ${startingDirName} not found.`) + return result + } + static titleToPermalink(str) { + return str + .replace(/[\/\_\:\\\[\]]/g, "-") + .replace(/π/g, "pi") + .replace(/`/g, "tick") + .replace(/\$/g, "dollar-sign") + .replace(/\*$/g, "-star") + .replace(/^\*/g, "star-") + .replace(/\*/g, "-star-") + .replace(/\'+$/g, "q") + .replace(/^@/g, "at-") + .replace(/@$/g, "-at") + .replace(/@/g, "-at-") + .replace(/[\'\"\,\ū]/g, "") + .replace(/^\#/g, "sharp-") + .replace(/\#$/g, "-sharp") + .replace(/\#/g, "-sharp-") + .replace(/[\(\)]/g, "") + .replace(/\+\+$/g, "pp") + .replace(/\+$/g, "p") + .replace(/^\!/g, "bang-") + .replace(/\!$/g, "-bang") + .replace(/\!/g, "-bang-") + .replace(/\&/g, "-n-") + .replace(/[\+ ]/g, "-") + .replace(/[^a-zA-Z0-9\-\.]/g, "") + .toLowerCase() + } + static escapeRegExp(str) { + return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + } + static sum(arr) { + return arr.reduce((curr, next) => curr + next, 0) + } + static makeVector(length, fill = 0) { + return new Array(length).fill(fill) + } + static makeMatrix(cols, rows, fill = 0) { + const matrix = [] + while (rows) { + matrix.push(Utils.makeVector(cols, fill)) + rows-- + } + return matrix + } + static removeNonAscii(str) { + // https://stackoverflow.com/questions/20856197/remove-non-ascii-character-in-string + return str.replace(/[^\x00-\x7F]/g, "") + } + static getMethodFromDotPath(context, str) { + const methodParts = str.split(".") + while (methodParts.length > 1) { + const methodName = methodParts.shift() + if (!context[methodName]) throw new Error(`${methodName} is not a method on ${context}`) + context = context[methodName]() + } + const final = methodParts.shift() + return [context, final] + } + static requireAbsOrRelative(filePath, contextFilePath) { + if (!filePath.startsWith(".")) return require(filePath) + const path = require("path") + const folder = this.getPathWithoutFileName(contextFilePath) + const file = path.resolve(folder + "/" + filePath) + return require(file) + } + // Removes last ".*" from this string + static removeFileExtension(filename) { + return filename ? filename.replace(/\.[^\.]+$/, "") : "" + } + static getFileName(path) { + const normalizedPath = path.replace(/\\/g, "/") + const parts = normalizedPath.split("/") + return parts.pop() + } + static getPathWithoutFileName(path) { + const normalizedPath = path.replace(/\\/g, "/") + const parts = normalizedPath.split("/") + parts.pop() + return parts.join("/") + } + static shuffleInPlace(arr, seed = Date.now()) { + // https://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array + const randFn = Utils._getPseudoRandom0to1FloatGenerator(seed) + for (let index = arr.length - 1; index > 0; index--) { + const tempIndex = Math.floor(randFn() * (index + 1)) + ;[arr[index], arr[tempIndex]] = [arr[tempIndex], arr[index]] + } + return arr + } + // Only allows a-zA-Z0-9-_ (And optionally .) + static _permalink(str, reg) { + return str.length ? str.toLowerCase().replace(reg, "").replace(/ /g, "-") : "" + } + static isValueEmpty(value) { + return value === undefined || value === "" || (typeof value === "number" && isNaN(value)) || (value instanceof Date && isNaN(value)) + } + static stringToPermalink(str) { + return this._permalink(str, /[^a-z0-9- _\.]/gi) + } + static getAvailablePermalink(permalink, doesFileExistSyncFn) { + const extension = this.getFileExtension(permalink) + permalink = this.removeFileExtension(permalink) + const originalPermalink = permalink + let num = 2 + let suffix = "" + let filename = `${originalPermalink}${suffix}.${extension}` + while (doesFileExistSyncFn(filename)) { + filename = `${originalPermalink}${suffix}.${extension}` + suffix = "-" + num + num++ + } + return filename + } + static getNextOrPrevious(arr, item) { + const length = arr.length + const index = arr.indexOf(item) + if (length === 1) return undefined + if (index === length - 1) return arr[index - 1] + return arr[index + 1] + } + static toggle(currentValue, values) { + const index = values.indexOf(currentValue) + return index === -1 || index + 1 === values.length ? values[0] : values[index + 1] + } + static getClassNameFromFilePath(filepath) { + return this.removeFileExtension(this.getFileName(filepath)) + } + static joinArraysOn(joinOn, arrays, columns) { + const rows = {} + let index = 0 + if (!columns) columns = arrays.map(arr => Object.keys(arr[0])) + arrays.forEach((arr, index) => { + const cols = columns[index] + arr.forEach(row => { + const key = joinOn ? row[joinOn] : index++ + if (!rows[key]) rows[key] = {} + const obj = rows[key] + cols.forEach(col => (obj[col] = row[col])) + }) + }) + return Object.values(rows) + } + static getParentFolder(path) { + if (path.endsWith("/")) path = this._removeLastSlash(path) + return path.replace(/\/[^\/]*$/, "") + "/" + } + static _removeLastSlash(path) { + return path.replace(/\/$/, "") + } + static _listToEnglishText(list, limit = 5) { + const len = list.length + if (!len) return "" + if (len === 1) return `'${list[0]}'` + const clone = list.slice(0, limit).map(item => `'${item}'`) + const last = clone.pop() + if (len <= limit) return clone.join(", ") + ` and ${last}` + return clone.join(", ") + ` and ${len - limit} more` + } + // todo: refactor so instead of str input takes an array of cells(strings) and scans each indepndently. + static _chooseDelimiter(str) { + const del = " ,|\t;^%$!#@~*&+-=_:?.{}[]()<>/".split("").find(idea => !str.includes(idea)) + if (!del) throw new Error("Could not find a delimiter") + return del + } + static flatten(arr) { + if (arr.flat) return arr.flat() + return arr.reduce((acc, val) => acc.concat(val), []) + } + static escapeBackTicks(str) { + return str.replace(/\`/g, "\\`").replace(/\$\{/g, "\\${") + } + static ucfirst(str) { + return str.charAt(0).toUpperCase() + str.slice(1) + } + // Adapted from: https://github.com/dcporter/didyoumean.js/blob/master/didYouMean-1.2.1.js + static didYouMean(str = "", options = [], caseSensitive = false, threshold = 0.4, thresholdAbsolute = 20) { + if (!caseSensitive) str = str.toLowerCase() + // Calculate the initial value (the threshold) if present. + const thresholdRelative = threshold * str.length + let maximumEditDistanceToBeBestMatch + if (thresholdRelative !== null && thresholdAbsolute !== null) maximumEditDistanceToBeBestMatch = Math.min(thresholdRelative, thresholdAbsolute) + else if (thresholdRelative !== null) maximumEditDistanceToBeBestMatch = thresholdRelative + else if (thresholdAbsolute !== null) maximumEditDistanceToBeBestMatch = thresholdAbsolute + // Get the edit distance to each option. If the closest one is less than 40% (by default) of str's length, then return it. + let closestMatch + const len = options.length + for (let optionIndex = 0; optionIndex < len; optionIndex++) { + const candidate = options[optionIndex] + if (!candidate) continue + const editDistance = Utils._getEditDistance(str, caseSensitive ? candidate : candidate.toLowerCase(), maximumEditDistanceToBeBestMatch) + if (editDistance < maximumEditDistanceToBeBestMatch) { + maximumEditDistanceToBeBestMatch = editDistance + closestMatch = candidate + } + } + return closestMatch + } + // Adapted from: https://github.com/dcporter/didyoumean.js/blob/master/didYouMean-1.2.1.js + static _getEditDistance(stringA, stringB, maxInt) { + // Handle null or undefined max. + maxInt = maxInt || maxInt === 0 ? maxInt : Utils.MAX_INT + const aLength = stringA.length + const bLength = stringB.length + // Fast path - no A or B. + if (aLength === 0) return Math.min(maxInt + 1, bLength) + if (bLength === 0) return Math.min(maxInt + 1, aLength) + // Fast path - length diff larger than max. + if (Math.abs(aLength - bLength) > maxInt) return maxInt + 1 + // Slow path. + const matrix = [] + // Set up the first row ([0, 1, 2, 3, etc]). + for (let bIndex = 0; bIndex <= bLength; bIndex++) { + matrix[bIndex] = [bIndex] + } + // Set up the first column (same). + for (let aIndex = 0; aIndex <= aLength; aIndex++) { + matrix[0][aIndex] = aIndex + } + let colMin + let minJ + let maxJ + // Loop over the rest of the columns. + for (let bIndex = 1; bIndex <= bLength; bIndex++) { + colMin = Utils.MAX_INT + minJ = 1 + if (bIndex > maxInt) minJ = bIndex - maxInt + maxJ = bLength + 1 + if (maxJ > maxInt + bIndex) maxJ = maxInt + bIndex + // Loop over the rest of the rows. + for (let aIndex = 1; aIndex <= aLength; aIndex++) { + // If j is out of bounds, just put a large value in the slot. + if (aIndex < minJ || aIndex > maxJ) matrix[bIndex][aIndex] = maxInt + 1 + // Otherwise do the normal Levenshtein thing. + else { + // If the characters are the same, there's no change in edit distance. + if (stringB.charAt(bIndex - 1) === stringA.charAt(aIndex - 1)) matrix[bIndex][aIndex] = matrix[bIndex - 1][aIndex - 1] + // Otherwise, see if we're substituting, inserting or deleting. + else + matrix[bIndex][aIndex] = Math.min( + matrix[bIndex - 1][aIndex - 1] + 1, // Substitute + Math.min( + matrix[bIndex][aIndex - 1] + 1, // Insert + matrix[bIndex - 1][aIndex] + 1 + ) + ) // Delete + } + // Either way, update colMin. + if (matrix[bIndex][aIndex] < colMin) colMin = matrix[bIndex][aIndex] + } + // If this column's minimum is greater than the allowed maximum, there's no point + // in going on with life. + if (colMin > maxInt) return maxInt + 1 + } + // If we made it this far without running into the max, then return the final matrix value. + return matrix[bLength][aLength] + } + static getLineIndexAtCharacterPosition(str, index) { + const lines = str.split("\n") + const len = lines.length + let position = 0 + for (let lineNumber = 0; lineNumber < len; lineNumber++) { + position += lines[lineNumber].length + if (position >= index) return lineNumber + } + } + static resolvePath(filePath, programFilepath) { + // For use in Node.js only + if (!filePath.startsWith(".")) return filePath + const path = require("path") + const folder = this.getPathWithoutFileName(programFilepath) + return path.resolve(folder + "/" + filePath) + } + static resolveProperty(obj, path, separator = ".") { + const properties = Array.isArray(path) ? path : path.split(separator) + return properties.reduce((prev, curr) => prev && prev[curr], obj) + } + static appendCodeAndReturnValueOnWindow(code, name) { + const script = document.createElement("script") + script.innerHTML = code + document.head.appendChild(script) + return window[name] + } + static formatStr(str, catchAllCellDelimiter = " ", parameterMap) { + return str.replace(/{([^\}]+)}/g, (match, path) => { + const val = parameterMap[path] + if (!val) return "" + return Array.isArray(val) ? val.join(catchAllCellDelimiter) : val + }) + } + static stripHtml(text) { + return text && text.replace ? text.replace(/<(?:.|\n)*?>/gm, "") : text + } + static getUniqueWordsArray(allWords) { + const words = allWords.replace(/\n/g, " ").split(" ") + const index = {} + words.forEach(word => { + if (!index[word]) index[word] = 0 + index[word]++ + }) + return Object.keys(index).map(key => { + return { + word: key, + count: index[key] + } + }) + } + static getRandomString(length = 30, letters = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".split(""), seed = Date.now()) { + let str = "" + const randFn = Utils._getPseudoRandom0to1FloatGenerator(seed) + while (length) { + str += letters[Math.round(Math.min(randFn() * letters.length, letters.length - 1))] + length-- + } + return str + } + // todo: add seed! + static makeRandomParticles(lines = 1000, seed = Date.now()) { + let str = "" + let letters = " 123abc".split("") + const randFn = Utils._getPseudoRandom0to1FloatGenerator(seed) + while (lines) { + let indent = " ".repeat(Math.round(randFn() * 6)) + let bit = indent + let rand = Math.floor(randFn() * 30) + while (rand) { + bit += letters[Math.round(Math.min(randFn() * letters.length, letters.length - 1))] + rand-- + } + bit += "\n" + str += bit + lines-- + } + return str + } + // adapted from https://gist.github.com/blixt/f17b47c62508be59987b + // 1993 Park-Miller LCG + static _getPseudoRandom0to1FloatGenerator(seed) { + return function () { + seed = Math.imul(48271, seed) | 0 % 2147483647 + return (seed & 2147483647) / 2147483648 + } + } + static sampleWithoutReplacement(population = [], quantity, seed) { + const prng = this._getPseudoRandom0to1FloatGenerator(seed) + const sampled = {} + const populationSize = population.length + if (quantity >= populationSize) return population.slice(0) + const picked = [] + while (picked.length < quantity) { + const index = Math.floor(prng() * populationSize) + if (sampled[index]) continue + sampled[index] = true + picked.push(population[index]) + } + return picked + } + static arrayToMap(arr) { + const map = {} + arr.forEach(val => (map[val] = true)) + return map + } + static _replaceNonAlphaNumericCharactersWithCharCodes(str) { + return str + .replace(/[^a-zA-Z0-9]/g, sub => { + return "_" + sub.charCodeAt(0).toString() + }) + .replace(/^([0-9])/, "number$1") + } + static mapValues(object, fn) { + const result = {} + Object.keys(object).forEach(key => { + result[key] = fn(key) + }) + return result + } + static javascriptTableWithHeaderRowToObjects(dataTable) { + dataTable = dataTable.slice() + const header = dataTable.shift() + return dataTable.map(row => { + const obj = {} + header.forEach((colName, index) => (obj[colName] = row[index])) + return obj + }) + } + static interweave(arrayOfArrays) { + const lineCount = Math.max(...arrayOfArrays.map(arr => arr.length)) + const totalArrays = arrayOfArrays.length + const result = [] + arrayOfArrays.forEach((lineArray, arrayIndex) => { + for (let lineIndex = 0; lineIndex < lineCount; lineIndex++) { + result[lineIndex * totalArrays + arrayIndex] = lineArray[lineIndex] + } + }) + return result + } + static makeSortByFn(accessorOrAccessors) { + const arrayOfFns = Array.isArray(accessorOrAccessors) ? accessorOrAccessors : [accessorOrAccessors] + return (objectA, objectB) => { + const particleAFirst = -1 + const particleBFirst = 1 + const accessor = arrayOfFns[0] // todo: handle accessors + const av = accessor(objectA) + const bv = accessor(objectB) + let result = av < bv ? particleAFirst : av > bv ? particleBFirst : 0 + if (av === undefined && bv !== undefined) result = particleAFirst + else if (bv === undefined && av !== undefined) result = particleBFirst + return result + } + } + static _makeGraphSortFunctionFromGraph(idAccessor, graph) { + return (particleA, particleB) => { + const particleAFirst = -1 + const particleBFirst = 1 + const particleAUniqueId = idAccessor(particleA) + const particleBUniqueId = idAccessor(particleB) + const particleAExtendsParticleB = graph[particleAUniqueId].has(particleBUniqueId) + const particleBExtendsParticleA = graph[particleBUniqueId].has(particleAUniqueId) + if (particleAExtendsParticleB) return particleBFirst + else if (particleBExtendsParticleA) return particleAFirst + const particleAExtendsSomething = graph[particleAUniqueId].size > 1 + const particleBExtendsSomething = graph[particleBUniqueId].size > 1 + if (!particleAExtendsSomething && particleBExtendsSomething) return particleAFirst + else if (!particleBExtendsSomething && particleAExtendsSomething) return particleBFirst + if (particleAUniqueId > particleBUniqueId) return particleBFirst + else if (particleAUniqueId < particleBUniqueId) return particleAFirst + return 0 + } + } + static removeAll(str, needle) { + return str.split(needle).join("") + } + static _makeGraphSortFunction(idAccessor, extendsIdAccessor) { + return (particleA, particleB) => { + // -1 === a before b + const particleAUniqueId = idAccessor(particleA) + const particleAExtends = extendsIdAccessor(particleA) + const particleBUniqueId = idAccessor(particleB) + const particleBExtends = extendsIdAccessor(particleB) + const particleAExtendsParticleB = particleAExtends === particleBUniqueId + const particleBExtendsParticleA = particleBExtends === particleAUniqueId + const particleAFirst = -1 + const particleBFirst = 1 + if (!particleAExtends && !particleBExtends) { + // If neither extends, sort by firstWord + if (particleAUniqueId > particleBUniqueId) return particleBFirst + else if (particleAUniqueId < particleBUniqueId) return particleAFirst + return 0 + } + // If only one extends, the other comes first + else if (!particleAExtends) return particleAFirst + else if (!particleBExtends) return particleBFirst + // If A extends B, B should come first + if (particleAExtendsParticleB) return particleBFirst + else if (particleBExtendsParticleA) return particleAFirst + // Sort by what they extend + if (particleAExtends > particleBExtends) return particleBFirst + else if (particleAExtends < particleBExtends) return particleAFirst + // Finally sort by firstWord + if (particleAUniqueId > particleBUniqueId) return particleBFirst + else if (particleAUniqueId < particleBUniqueId) return particleAFirst + // Should never hit this, unless we have a duplicate line. + return 0 + } + } +} +Utils.Timer = Timer +//http://stackoverflow.com/questions/37684/how-to-replace-plain-urls-with-links#21925491 +Utils.linkify = (text, target = "_blank") => { + let replacedText + let replacePattern1 + let replacePattern2 + let replacePattern3 + //URLs starting with http://, https://, or ftp:// + replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z\(\)0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+\(\)&@#\/%=~_|])/gim + replacedText = text.replace(replacePattern1, `$1`) + //URLs starting with "www." (without // before it, or it'd re-link the ones done above). + replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim + replacedText = replacedText.replace(replacePattern2, `$1$2`) + //Change email addresses to mailto:: links. + replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim + replacedText = replacedText.replace(replacePattern3, '$1') + return replacedText +} +// todo: switch algo to: http://indiegamr.com/generate-repeatable-random-numbers-in-js/? +Utils.makeSemiRandomFn = (seed = Date.now()) => { + return () => { + const semiRand = Math.sin(seed++) * 10000 + return semiRand - Math.floor(semiRand) + } +} +Utils.randomUniformInt = (min, max, seed = Date.now()) => { + return Math.floor(Utils.randomUniformFloat(min, max, seed)) +} +Utils.randomUniformFloat = (min, max, seed = Date.now()) => { + const randFn = Utils.makeSemiRandomFn(seed) + return min + (max - min) * randFn() +} +Utils.getRange = (startIndex, endIndexExclusive, increment = 1) => { + const range = [] + for (let index = startIndex; index < endIndexExclusive; index = index + increment) { + range.push(index) + } + return range +} +Utils.MAX_INT = Math.pow(2, 32) - 1 +window.Utils = Utils + + +let _scrollsdkLatestTime = 0 +let _scrollsdkMinTimeIncrement = 0.000000000001 +class AbstractParticle { + _getProcessTimeInMilliseconds() { + // We add this loop to restore monotonically increasing .now(): + // https://developer.mozilla.org/en-US/docs/Web/API/Performance/now + let time = performance.now() + while (time <= _scrollsdkLatestTime) { + if (time === time + _scrollsdkMinTimeIncrement) + // Some browsers have different return values for perf.now() + _scrollsdkMinTimeIncrement = 10 * _scrollsdkMinTimeIncrement + time += _scrollsdkMinTimeIncrement + } + _scrollsdkLatestTime = time + return time + } +} +var FileFormat +;(function (FileFormat) { + FileFormat["csv"] = "csv" + FileFormat["tsv"] = "tsv" + FileFormat["particles"] = "particles" +})(FileFormat || (FileFormat = {})) +const TN_WORD_BREAK_SYMBOL = " " +const TN_EDGE_SYMBOL = " " +const TN_NODE_BREAK_SYMBOL = "\n" +class AbstractParticleEvent { + constructor(targetParticle) { + this.targetParticle = targetParticle + } +} +class ChildAddedParticleEvent extends AbstractParticleEvent {} +class ChildRemovedParticleEvent extends AbstractParticleEvent {} +class DescendantChangedParticleEvent extends AbstractParticleEvent {} +class LineChangedParticleEvent extends AbstractParticleEvent {} +class ParticleWord { + constructor(particle, cellIndex) { + this._particle = particle + this._cellIndex = cellIndex + } + replace(newWord) { + this._particle.setWord(this._cellIndex, newWord) + } + get word() { + return this._particle.getWord(this._cellIndex) + } +} +const ParticleEvents = { ChildAddedParticleEvent, ChildRemovedParticleEvent, DescendantChangedParticleEvent, LineChangedParticleEvent } +var WhereOperators +;(function (WhereOperators) { + WhereOperators["equal"] = "=" + WhereOperators["notEqual"] = "!=" + WhereOperators["lessThan"] = "<" + WhereOperators["lessThanOrEqual"] = "<=" + WhereOperators["greaterThan"] = ">" + WhereOperators["greaterThanOrEqual"] = ">=" + WhereOperators["includes"] = "includes" + WhereOperators["doesNotInclude"] = "doesNotInclude" + WhereOperators["in"] = "in" + WhereOperators["notIn"] = "notIn" + WhereOperators["empty"] = "empty" + WhereOperators["notEmpty"] = "notEmpty" +})(WhereOperators || (WhereOperators = {})) +var ParticlesConstants +;(function (ParticlesConstants) { + ParticlesConstants["extends"] = "extends" +})(ParticlesConstants || (ParticlesConstants = {})) +class ParserCombinator { + constructor(catchAllParser, firstWordMap = {}, regexTests = undefined) { + this._catchAllParser = catchAllParser + this._firstWordMap = new Map(Object.entries(firstWordMap)) + this._regexTests = regexTests + } + getFirstWordOptions() { + return Array.from(this._getFirstWordMap().keys()) + } + // todo: remove + _getFirstWordMap() { + return this._firstWordMap + } + // todo: remove + _getFirstWordMapAsObject() { + let obj = {} + const map = this._getFirstWordMap() + for (let [key, val] of map.entries()) { + obj[key] = val + } + return obj + } + _getParser(line, contextParticle, wordBreakSymbol = TN_WORD_BREAK_SYMBOL) { + return this._getFirstWordMap().get(this._getFirstWord(line, wordBreakSymbol)) || this._getParserFromRegexTests(line) || this._getCatchAllParser(contextParticle) + } + _getCatchAllParser(contextParticle) { + if (this._catchAllParser) return this._catchAllParser + const parent = contextParticle.parent + if (parent) return parent._getParser()._getCatchAllParser(parent) + return contextParticle.constructor + } + _getParserFromRegexTests(line) { + if (!this._regexTests) return undefined + const hit = this._regexTests.find(test => test.regex.test(line)) + if (hit) return hit.parser + return undefined + } + _getFirstWord(line, wordBreakSymbol) { + const firstBreak = line.indexOf(wordBreakSymbol) + return line.substr(0, firstBreak > -1 ? firstBreak : undefined) + } +} +class Particle extends AbstractParticle { + constructor(children, line, parent) { + super() + // BEGIN MUTABLE METHODS BELOw + this._particleCreationTime = this._getProcessTimeInMilliseconds() + this._parent = parent + this._setLine(line) + this._setChildren(children) + } + execute() {} + async loadRequirements(context) { + // todo: remove + await Promise.all(this.map(particle => particle.loadRequirements(context))) + } + getErrors() { + return [] + } + get lineCellTypes() { + // todo: make this any a constant + return "undefinedCellType ".repeat(this.words.length).trim() + } + isNodeJs() { + return typeof exports !== "undefined" + } + isBrowser() { + return !this.isNodeJs() + } + getOlderSiblings() { + if (this.isRoot()) return [] + return this.parent.slice(0, this.getIndex()) + } + _getClosestOlderSibling() { + const olderSiblings = this.getOlderSiblings() + return olderSiblings[olderSiblings.length - 1] + } + getYoungerSiblings() { + if (this.isRoot()) return [] + return this.parent.slice(this.getIndex() + 1) + } + getSiblings() { + if (this.isRoot()) return [] + return this.parent.filter(particle => particle !== this) + } + _getUid() { + if (!this._uid) this._uid = Particle._makeUniqueId() + return this._uid + } + // todo: rename getMother? grandMother et cetera? + get parent() { + return this._parent + } + getIndentLevel(relativeTo) { + return this._getIndentLevel(relativeTo) + } + get indentation() { + const indentLevel = this._getIndentLevel() - 1 + if (indentLevel < 0) return "" + return this.edgeSymbol.repeat(indentLevel) + } + _getTopDownArray(arr) { + this.forEach(child => { + arr.push(child) + child._getTopDownArray(arr) + }) + } + get topDownArray() { + const arr = [] + this._getTopDownArray(arr) + return arr + } + *getTopDownArrayIterator() { + for (let child of this.getChildren()) { + yield child + yield* child.getTopDownArrayIterator() + } + } + particleAtLine(lineNumber) { + let index = 0 + for (let particle of this.getTopDownArrayIterator()) { + if (lineNumber === index) return particle + index++ + } + } + get numberOfLines() { + let lineCount = 0 + for (let particle of this.getTopDownArrayIterator()) { + lineCount++ + } + return lineCount + } + _getMaxUnitsOnALine() { + let max = 0 + for (let particle of this.getTopDownArrayIterator()) { + const count = particle.words.length + particle.getIndentLevel() + if (count > max) max = count + } + return max + } + get numberOfWords() { + let wordCount = 0 + for (let particle of this.getTopDownArrayIterator()) { + wordCount += particle.words.length + } + return wordCount + } + get lineNumber() { + return this._getLineNumberRelativeTo() + } + _getLineNumber(target = this) { + if (this._cachedLineNumber) return this._cachedLineNumber + let lineNumber = 1 + for (let particle of this.root.getTopDownArrayIterator()) { + if (particle === target) return lineNumber + lineNumber++ + } + return lineNumber + } + isBlankLine() { + return !this.length && !this.getLine() + } + hasDuplicateFirstWords() { + return this.length ? new Set(this.getFirstWords()).size !== this.length : false + } + isEmpty() { + return !this.length && !this.content + } + _getLineNumberRelativeTo(relativeTo) { + if (this.isRoot(relativeTo)) return 0 + const start = relativeTo || this.root + return start._getLineNumber(this) + } + isRoot(relativeTo) { + return relativeTo === this || !this.parent + } + get root() { + return this._getRootParticle() + } + _getRootParticle(relativeTo) { + if (this.isRoot(relativeTo)) return this + return this.parent._getRootParticle(relativeTo) + } + toString(indentCount = 0, language = this) { + if (this.isRoot()) return this._childrenToString(indentCount, language) + return language.edgeSymbol.repeat(indentCount) + this.getLine(language) + (this.length ? language.particleBreakSymbol + this._childrenToString(indentCount + 1, language) : "") + } + get asString() { + return this.toString() + } + printLinesFrom(start, quantity) { + return this._printLinesFrom(start, quantity, false) + } + printLinesWithLineNumbersFrom(start, quantity) { + return this._printLinesFrom(start, quantity, true) + } + _printLinesFrom(start, quantity, printLineNumbers) { + // todo: use iterator for better perf? + const end = start + quantity + this.toString() + .split("\n") + .slice(start, end) + .forEach((line, index) => { + if (printLineNumbers) console.log(`${start + index} ${line}`) + else console.log(line) + }) + return this + } + getWord(index) { + const words = this._getWords(0) + if (index < 0) index = words.length + index + return words[index] + } + get list() { + return this.getWordsFrom(1) + } + _toHtml(indentCount) { + const path = this.getPathVector().join(" ") + const classes = { + particleLine: "particleLine", + edgeSymbol: "edgeSymbol", + particleBreakSymbol: "particleBreakSymbol", + particleChildren: "particleChildren" + } + const edge = this.edgeSymbol.repeat(indentCount) + // Set up the firstWord part of the particle + const edgeHtml = `${edge}` + const lineHtml = this._getLineHtml() + const childrenHtml = this.length ? `${this.particleBreakSymbol}` + `${this._childrenToHtml(indentCount + 1)}` : "" + return `${edgeHtml}${lineHtml}${childrenHtml}` + } + _getWords(startFrom) { + if (!this._words) this._words = this._getLine().split(this.wordBreakSymbol) + return startFrom ? this._words.slice(startFrom) : this._words + } + get words() { + return this._getWords(0) + } + doesExtend(parserId) { + return false + } + require(moduleName, filePath) { + if (!this.isNodeJs()) return window[moduleName] + return require(filePath || moduleName) + } + getWordsFrom(startFrom) { + return this._getWords(startFrom) + } + getFirstAncestor() { + const parent = this.parent + return parent.isRoot() ? this : parent.getFirstAncestor() + } + isLoaded() { + return true + } + getRunTimePhaseErrors() { + if (!this._runTimePhaseErrors) this._runTimePhaseErrors = {} + return this._runTimePhaseErrors + } + setRunTimePhaseError(phase, errorObject) { + if (errorObject === undefined) delete this.getRunTimePhaseErrors()[phase] + else this.getRunTimePhaseErrors()[phase] = errorObject + return this + } + _getJavascriptPrototypeChainUpTo(stopAtClassName = "Particle") { + // todo: cross browser test this + let constructor = this.constructor + const chain = [] + while (constructor.name !== stopAtClassName) { + chain.unshift(constructor.name) + constructor = constructor.__proto__ + } + chain.unshift(stopAtClassName) + return chain + } + _getProjectRootDir() { + return this.isRoot() ? "" : this.root._getProjectRootDir() + } + // Concat 2 particles amd return a new particle, but replace any particles + // in this particle that start with the same particle from the first particle with + // that patched version. Does not recurse. + patch(two) { + const copy = this.clone() + two.forEach(particle => { + const hit = copy.getParticle(particle.getWord(0)) + if (hit) hit.destroy() + }) + copy.concat(two) + return copy + } + getSparsity() { + const particles = this.getChildren() + const fields = this._getUnionNames() + let count = 0 + this.getChildren().forEach(particle => { + fields.forEach(field => { + if (particle.has(field)) count++ + }) + }) + return 1 - count / (particles.length * fields.length) + } + // todo: rename. what is the proper term from set/cat theory? + getBiDirectionalMaps(propertyNameOrFn, propertyNameOrFn2 = particle => particle.getWord(0)) { + const oneToTwo = {} + const twoToOne = {} + const is1Str = typeof propertyNameOrFn === "string" + const is2Str = typeof propertyNameOrFn2 === "string" + const children = this.getChildren() + this.forEach((particle, index) => { + const value1 = is1Str ? particle.get(propertyNameOrFn) : propertyNameOrFn(particle, index, children) + const value2 = is2Str ? particle.get(propertyNameOrFn2) : propertyNameOrFn2(particle, index, children) + if (value1 !== undefined) { + if (!oneToTwo[value1]) oneToTwo[value1] = [] + oneToTwo[value1].push(value2) + } + if (value2 !== undefined) { + if (!twoToOne[value2]) twoToOne[value2] = [] + twoToOne[value2].push(value1) + } + }) + return [oneToTwo, twoToOne] + } + _getWordIndexCharacterStartPosition(wordIndex) { + const xiLength = this.edgeSymbol.length + const numIndents = this._getIndentLevel() - 1 + const indentPosition = xiLength * numIndents + if (wordIndex < 1) return xiLength * (numIndents + wordIndex) + return indentPosition + this.words.slice(0, wordIndex).join(this.wordBreakSymbol).length + this.wordBreakSymbol.length + } + getParticleInScopeAtCharIndex(charIndex) { + if (this.isRoot()) return this + let wordIndex = this.getWordIndexAtCharacterIndex(charIndex) + if (wordIndex > 0) return this + let particle = this + while (wordIndex < 1) { + particle = particle.parent + wordIndex++ + } + return particle + } + getWordProperties(wordIndex) { + const start = this._getWordIndexCharacterStartPosition(wordIndex) + const word = wordIndex < 0 ? "" : this.getWord(wordIndex) + return { + startCharIndex: start, + endCharIndex: start + word.length, + word: word + } + } + fill(fill = "") { + this.topDownArray.forEach(line => { + line.words.forEach((word, index) => line.setWord(index, fill)) + }) + return this + } + getAllWordBoundaryCoordinates() { + const coordinates = [] + let lineIndex = 0 + for (let particle of this.getTopDownArrayIterator()) { + particle.getWordBoundaryCharIndices().forEach((charIndex, wordIndex) => { + coordinates.push({ + lineIndex: lineIndex, + charIndex: charIndex, + wordIndex: wordIndex + }) + }) + lineIndex++ + } + return coordinates + } + getWordBoundaryCharIndices() { + let indentLevel = this._getIndentLevel() + const wordBreakSymbolLength = this.wordBreakSymbol.length + let elapsed = indentLevel + return this.words.map((word, wordIndex) => { + const boundary = elapsed + elapsed += word.length + wordBreakSymbolLength + return boundary + }) + } + getWordIndexAtCharacterIndex(charIndex) { + // todo: is this correct thinking for handling root? + if (this.isRoot()) return 0 + const numberOfIndents = this._getIndentLevel(undefined) - 1 + // todo: probably want to rewrite this in a performant way. + const spots = [] + while (spots.length < numberOfIndents) { + spots.push(-(numberOfIndents - spots.length)) + } + this.words.forEach((word, wordIndex) => { + word.split("").forEach(letter => { + spots.push(wordIndex) + }) + spots.push(wordIndex) + }) + return spots[charIndex] + } + // Note: This currently does not return any errors resulting from "required" or "single" + getAllErrors(lineStartsAt = 1) { + const errors = [] + for (let particle of this.topDownArray) { + particle._cachedLineNumber = lineStartsAt // todo: cleanup + const errs = particle.getErrors() + errs.forEach(err => errors.push(err)) + // delete particle._cachedLineNumber + lineStartsAt++ + } + return errors + } + *getAllErrorsIterator() { + let line = 1 + for (let particle of this.getTopDownArrayIterator()) { + particle._cachedLineNumber = line + const errs = particle.getErrors() + // delete particle._cachedLineNumber + if (errs.length) yield errs + line++ + } + } + get firstWord() { + return this.words[0] + } + get content() { + const words = this.getWordsFrom(1) + return words.length ? words.join(this.wordBreakSymbol) : undefined + } + get contentWithChildren() { + // todo: deprecate + const content = this.content + return (content ? content : "") + (this.length ? this.particleBreakSymbol + this._childrenToString() : "") + } + getFirstParticle() { + return this.particleAt(0) + } + getStack() { + return this._getStack() + } + _getStack(relativeTo) { + if (this.isRoot(relativeTo)) return [] + const parent = this.parent + if (parent.isRoot(relativeTo)) return [this] + else return parent._getStack(relativeTo).concat([this]) + } + getStackString() { + return this._getStack() + .map((particle, index) => this.edgeSymbol.repeat(index) + particle.getLine()) + .join(this.particleBreakSymbol) + } + getLine(language) { + if (!this._words && !language) return this._getLine() // todo: how does this interact with "language" param? + return this.words.join((language || this).wordBreakSymbol) + } + getColumnNames() { + return this._getUnionNames() + } + getOneHot(column) { + const clone = this.clone() + const cols = Array.from(new Set(clone.getColumn(column))) + clone.forEach(particle => { + const val = particle.get(column) + particle.delete(column) + cols.forEach(col => { + particle.set(column + "_" + col, val === col ? "1" : "0") + }) + }) + return clone + } + // todo: return array? getPathArray? + _getFirstWordPath(relativeTo) { + if (this.isRoot(relativeTo)) return "" + else if (this.parent.isRoot(relativeTo)) return this.firstWord + return this.parent._getFirstWordPath(relativeTo) + this.edgeSymbol + this.firstWord + } + getFirstWordPathRelativeTo(relativeTo) { + return this._getFirstWordPath(relativeTo) + } + getFirstWordPath() { + return this._getFirstWordPath() + } + getPathVector() { + return this._getPathVector() + } + getPathVectorRelativeTo(relativeTo) { + return this._getPathVector(relativeTo) + } + _getPathVector(relativeTo) { + if (this.isRoot(relativeTo)) return [] + const path = this.parent._getPathVector(relativeTo) + path.push(this.getIndex()) + return path + } + getIndex() { + return this.parent._indexOfParticle(this) + } + isTerminal() { + return !this.length + } + _getLineHtml() { + return this.words.map((word, index) => `${Utils.stripHtml(word)}`).join(`${this.wordBreakSymbol}`) + } + _getXmlContent(indentCount) { + if (this.content !== undefined) return this.contentWithChildren + return this.length ? `${indentCount === -1 ? "" : "\n"}${this._childrenToXml(indentCount > -1 ? indentCount + 2 : -1)}${" ".repeat(indentCount)}` : "" + } + _toXml(indentCount) { + const indent = " ".repeat(indentCount) + const tag = this.firstWord + return `${indent}<${tag}>${this._getXmlContent(indentCount)}${indentCount === -1 ? "" : "\n"}` + } + _toObjectTuple() { + const content = this.content + const length = this.length + const hasChildrenNoContent = content === undefined && length + const hasContentAndHasChildren = content !== undefined && length + // If the particle has a content and a subparticle return it as a string, as + // Javascript object values can't be both a leaf and a particle. + const tupleValue = hasChildrenNoContent ? this.toObject() : hasContentAndHasChildren ? this.contentWithChildren : content + return [this.firstWord, tupleValue] + } + _indexOfParticle(needleParticle) { + let result = -1 + this.find((particle, index) => { + if (particle === needleParticle) { + result = index + return true + } + }) + return result + } + getMaxLineWidth() { + let maxWidth = 0 + for (let particle of this.getTopDownArrayIterator()) { + const lineWidth = particle.getLine().length + if (lineWidth > maxWidth) maxWidth = lineWidth + } + return maxWidth + } + toParticle() { + return new Particle(this.toString()) + } + _rightPad(newWidth, padCharacter) { + const line = this.getLine() + this.setLine(line + padCharacter.repeat(newWidth - line.length)) + return this + } + rightPad(padCharacter = " ") { + const newWidth = this.getMaxLineWidth() + this.topDownArray.forEach(particle => particle._rightPad(newWidth, padCharacter)) + return this + } + lengthen(numberOfLines) { + let linesToAdd = numberOfLines - this.numberOfLines + while (linesToAdd > 0) { + this.appendLine("") + linesToAdd-- + } + return this + } + toSideBySide(particlesOrStrings, delimiter = " ") { + particlesOrStrings = particlesOrStrings.map(particle => (particle instanceof Particle ? particle : new Particle(particle))) + const clone = this.toParticle() + const particleBreakSymbol = "\n" + let next + while ((next = particlesOrStrings.shift())) { + clone.lengthen(next.numberOfLines) + clone.rightPad() + next + .toString() + .split(particleBreakSymbol) + .forEach((line, index) => { + const particle = clone.particleAtLine(index) + particle.setLine(particle.getLine() + delimiter + line) + }) + } + return clone + } + toComparison(particle) { + const particleBreakSymbol = "\n" + const lines = particle.toString().split(particleBreakSymbol) + return new Particle( + this.toString() + .split(particleBreakSymbol) + .map((line, index) => (lines[index] === line ? "" : "x")) + .join(particleBreakSymbol) + ) + } + toBraid(particlesOrStrings) { + particlesOrStrings.unshift(this) + const particleDelimiter = this.particleBreakSymbol + return new Particle( + Utils.interweave(particlesOrStrings.map(particle => particle.toString().split(particleDelimiter))) + .map(line => (line === undefined ? "" : line)) + .join(particleDelimiter) + ) + } + getSlice(startIndexInclusive, stopIndexExclusive) { + return new Particle( + this.slice(startIndexInclusive, stopIndexExclusive) + .map(child => child.toString()) + .join("\n") + ) + } + _hasColumns(columns) { + const words = this.words + return columns.every((searchTerm, index) => searchTerm === words[index]) + } + hasWord(index, word) { + return this.getWord(index) === word + } + getParticleByColumns(...columns) { + return this.topDownArray.find(particle => particle._hasColumns(columns)) + } + getParticleByColumn(index, name) { + return this.find(particle => particle.getWord(index) === name) + } + _getParticlesByColumn(index, name) { + return this.filter(particle => particle.getWord(index) === name) + } + // todo: preserve subclasses! + select(columnNames) { + columnNames = Array.isArray(columnNames) ? columnNames : [columnNames] + const result = new Particle() + this.forEach(particle => { + const newParticle = result.appendLine(particle.getLine()) + columnNames.forEach(name => { + const valueParticle = particle.getParticle(name) + if (valueParticle) newParticle.appendParticle(valueParticle) + }) + }) + return result + } + selectionToString() { + return this.getSelectedParticles() + .map(particle => particle.toString()) + .join("\n") + } + getSelectedParticles() { + return this.topDownArray.filter(particle => particle.isSelected()) + } + clearSelection() { + this.getSelectedParticles().forEach(particle => particle.unselectParticle()) + } + // Note: this is for debugging select chains + print(message = "") { + if (message) console.log(message) + console.log(this.toString()) + return this + } + // todo: preserve subclasses! + // todo: preserve links back to parent so you could edit as normal? + where(columnName, operator, fixedValue) { + const isArray = Array.isArray(fixedValue) + const valueType = isArray ? typeof fixedValue[0] : typeof fixedValue + let parser + if (valueType === "number") parser = parseFloat + const fn = particle => { + const cell = particle.get(columnName) + const typedCell = parser ? parser(cell) : cell + if (operator === WhereOperators.equal) return fixedValue === typedCell + else if (operator === WhereOperators.notEqual) return fixedValue !== typedCell + else if (operator === WhereOperators.includes) return typedCell !== undefined && typedCell.includes(fixedValue) + else if (operator === WhereOperators.doesNotInclude) return typedCell === undefined || !typedCell.includes(fixedValue) + else if (operator === WhereOperators.greaterThan) return typedCell > fixedValue + else if (operator === WhereOperators.lessThan) return typedCell < fixedValue + else if (operator === WhereOperators.greaterThanOrEqual) return typedCell >= fixedValue + else if (operator === WhereOperators.lessThanOrEqual) return typedCell <= fixedValue + else if (operator === WhereOperators.empty) return !particle.has(columnName) + else if (operator === WhereOperators.notEmpty) return particle.has(columnName) || (cell !== "" && cell !== undefined) + else if (operator === WhereOperators.in && isArray) return fixedValue.includes(typedCell) + else if (operator === WhereOperators.notIn && isArray) return !fixedValue.includes(typedCell) + } + const result = new Particle() + this.filter(fn).forEach(particle => { + result.appendParticle(particle) + }) + return result + } + with(firstWord) { + return this.filter(particle => particle.has(firstWord)) + } + without(firstWord) { + return this.filter(particle => !particle.has(firstWord)) + } + first(quantity = 1) { + return this.limit(quantity, 0) + } + last(quantity = 1) { + return this.limit(quantity, this.length - quantity) + } + // todo: preserve subclasses! + limit(quantity, offset = 0) { + const result = new Particle() + this.getChildren() + .slice(offset, quantity + offset) + .forEach(particle => { + result.appendParticle(particle) + }) + return result + } + getChildrenFirstArray() { + const arr = [] + this._getChildrenFirstArray(arr) + return arr + } + _getChildrenFirstArray(arr) { + this.forEach(child => { + child._getChildrenFirstArray(arr) + arr.push(child) + }) + } + _getIndentLevel(relativeTo) { + return this._getStack(relativeTo).length + } + getParentFirstArray() { + const levels = this._getLevels() + const arr = [] + Object.values(levels).forEach(level => { + level.forEach(item => arr.push(item)) + }) + return arr + } + _getLevels() { + const levels = {} + this.topDownArray.forEach(particle => { + const level = particle._getIndentLevel() + if (!levels[level]) levels[level] = [] + levels[level].push(particle) + }) + return levels + } + _getChildrenArray() { + if (!this._children) this._children = [] + return this._children + } + getLines() { + return this.map(particle => particle.getLine()) + } + getChildren() { + return this._getChildrenArray().slice(0) + } + get length() { + return this._getChildrenArray().length + } + _particleAt(index) { + if (index < 0) index = this.length + index + return this._getChildrenArray()[index] + } + particleAt(indexOrIndexArray) { + if (typeof indexOrIndexArray === "number") return this._particleAt(indexOrIndexArray) + if (indexOrIndexArray.length === 1) return this._particleAt(indexOrIndexArray[0]) + const first = indexOrIndexArray[0] + const particle = this._particleAt(first) + if (!particle) return undefined + return particle.particleAt(indexOrIndexArray.slice(1)) + } + // Flatten a particle into an object like {twitter:"pldb", "twitter.followers":123}. + // Assumes you have a nested key/value list with no multiline strings. + toFlatObject(delimiter = ".") { + let newObject = {} + const { edgeSymbolRegex } = this + this.forEach((child, index) => { + newObject[child.getWord(0)] = child.content + child.topDownArray.forEach(particle => { + const newColumnName = particle.getFirstWordPathRelativeTo(this).replace(edgeSymbolRegex, delimiter) + const value = particle.content + newObject[newColumnName] = value + }) + }) + return newObject + } + _toObject() { + const obj = {} + this.forEach(particle => { + const tuple = particle._toObjectTuple() + obj[tuple[0]] = tuple[1] + }) + return obj + } + get asHtml() { + return this._childrenToHtml(0) + } + _toHtmlCubeLine(indents = 0, lineIndex = 0, planeIndex = 0) { + const getLine = (cellIndex, word = "") => + `${word}` + let cells = [] + this.words.forEach((word, index) => (word ? cells.push(getLine(index + indents, word)) : "")) + return cells.join("") + } + get asHtmlCube() { + return this.map((plane, planeIndex) => plane.topDownArray.map((line, lineIndex) => line._toHtmlCubeLine(line.getIndentLevel() - 2, lineIndex, planeIndex)).join("")).join("") + } + _getHtmlJoinByCharacter() { + return `${this.particleBreakSymbol}` + } + _childrenToHtml(indentCount) { + const joinBy = this._getHtmlJoinByCharacter() + return this.map(particle => particle._toHtml(indentCount)).join(joinBy) + } + _childrenToString(indentCount, language = this) { + return this.map(particle => particle.toString(indentCount, language)).join(language.particleBreakSymbol) + } + childrenToString(indentCount = 0) { + return this._childrenToString(indentCount) + } + // todo: implement + _getChildJoinCharacter() { + return "\n" + } + format() { + this.forEach(child => child.format()) + return this + } + compile() { + return this.map(child => child.compile()).join(this._getChildJoinCharacter()) + } + get asXml() { + return this._childrenToXml(0) + } + toDisk(path) { + if (!this.isNodeJs()) throw new Error("This method only works in Node.js") + const format = Particle._getFileFormat(path) + const formats = { + particles: particle => particle.toString(), + csv: particle => particle.asCsv, + tsv: particle => particle.asTsv + } + this.require("fs").writeFileSync(path, formats[format](this), "utf8") + return this + } + _lineToYaml(indentLevel, listTag = "") { + let prefix = " ".repeat(indentLevel) + if (listTag && indentLevel > 1) prefix = " ".repeat(indentLevel - 2) + listTag + " " + return prefix + `${this.firstWord}:` + (this.content ? " " + this.content : "") + } + _isYamlList() { + return this.hasDuplicateFirstWords() + } + get asYaml() { + return `%YAML 1.2 +---\n${this._childrenToYaml(0).join("\n")}` + } + _childrenToYaml(indentLevel) { + if (this._isYamlList()) return this._childrenToYamlList(indentLevel) + else return this._childrenToYamlAssociativeArray(indentLevel) + } + // if your code-to-be-yaml has a list of associative arrays of type N and you don't + // want the type N to print + _collapseYamlLine() { + return false + } + _toYamlListElement(indentLevel) { + const children = this._childrenToYaml(indentLevel + 1) + if (this._collapseYamlLine()) { + if (indentLevel > 1) return children.join("\n").replace(" ".repeat(indentLevel), " ".repeat(indentLevel - 2) + "- ") + return children.join("\n") + } else { + children.unshift(this._lineToYaml(indentLevel, "-")) + return children.join("\n") + } + } + _childrenToYamlList(indentLevel) { + return this.map(particle => particle._toYamlListElement(indentLevel + 2)) + } + _toYamlAssociativeArrayElement(indentLevel) { + const children = this._childrenToYaml(indentLevel + 1) + children.unshift(this._lineToYaml(indentLevel)) + return children.join("\n") + } + _childrenToYamlAssociativeArray(indentLevel) { + return this.map(particle => particle._toYamlAssociativeArrayElement(indentLevel)) + } + get asJsonSubset() { + return JSON.stringify(this.toObject(), null, " ") + } + _toObjectForSerialization() { + return this.length + ? { + cells: this.words, + children: this.map(child => child._toObjectForSerialization()) + } + : { + cells: this.words + } + } + get asJson() { + return JSON.stringify({ children: this.map(child => child._toObjectForSerialization()) }, null, " ") + } + get asGrid() { + const WordBreakSymbol = this.wordBreakSymbol + return this.toString() + .split(this.particleBreakSymbol) + .map(line => line.split(WordBreakSymbol)) + } + get asGridJson() { + return JSON.stringify(this.asGrid, null, 2) + } + findParticles(firstWordPath) { + // todo: can easily speed this up + const map = {} + if (!Array.isArray(firstWordPath)) firstWordPath = [firstWordPath] + firstWordPath.forEach(path => (map[path] = true)) + return this.topDownArray.filter(particle => { + if (map[particle._getFirstWordPath(this)]) return true + return false + }) + } + evalTemplateString(str) { + const that = this + return str.replace(/{([^\}]+)}/g, (match, path) => that.get(path) || "") + } + emitLogMessage(message) { + console.log(message) + } + getColumn(path) { + return this.map(particle => particle.get(path)) + } + getFiltered(fn) { + const clone = this.clone() + clone + .filter((particle, index) => !fn(particle, index)) + .forEach(particle => { + particle.destroy() + }) + return clone + } + getParticle(firstWordPath) { + return this._getParticleByPath(firstWordPath) + } + getFrom(prefix) { + const hit = this.filter(particle => particle.getLine().startsWith(prefix))[0] + if (hit) return hit.getLine().substr((prefix + this.wordBreakSymbol).length) + } + get(firstWordPath) { + const particle = this._getParticleByPath(firstWordPath) + return particle === undefined ? undefined : particle.content + } + getOneOf(keys) { + for (let i = 0; i < keys.length; i++) { + const value = this.get(keys[i]) + if (value) return value + } + return "" + } + pick(fields) { + const newParticle = new Particle(this.toString()) // todo: why not clone? + const map = Utils.arrayToMap(fields) + newParticle.particleAt(0).forEach(particle => { + if (!map[particle.getWord(0)]) particle.destroy() + }) + return newParticle + } + getParticlesByGlobPath(query) { + return this._getParticlesByGlobPath(query) + } + _getParticlesByGlobPath(globPath) { + const edgeSymbol = this.edgeSymbol + if (!globPath.includes(edgeSymbol)) { + if (globPath === "*") return this.getChildren() + return this.filter(particle => particle.firstWord === globPath) + } + const parts = globPath.split(edgeSymbol) + const current = parts.shift() + const rest = parts.join(edgeSymbol) + const matchingParticles = current === "*" ? this.getChildren() : this.filter(child => child.firstWord === current) + return [].concat.apply( + [], + matchingParticles.map(particle => particle._getParticlesByGlobPath(rest)) + ) + } + _getParticleByPath(firstWordPath) { + const edgeSymbol = this.edgeSymbol + if (!firstWordPath.includes(edgeSymbol)) { + const index = this.indexOfLast(firstWordPath) + return index === -1 ? undefined : this._particleAt(index) + } + const parts = firstWordPath.split(edgeSymbol) + const current = parts.shift() + const currentParticle = this._getChildrenArray()[this._getIndex()[current]] + return currentParticle ? currentParticle._getParticleByPath(parts.join(edgeSymbol)) : undefined + } + get next() { + if (this.isRoot()) return this + const index = this.getIndex() + const parent = this.parent + const length = parent.length + const next = index + 1 + return next === length ? parent._getChildrenArray()[0] : parent._getChildrenArray()[next] + } + get previous() { + if (this.isRoot()) return this + const index = this.getIndex() + const parent = this.parent + const length = parent.length + const prev = index - 1 + return prev === -1 ? parent._getChildrenArray()[length - 1] : parent._getChildrenArray()[prev] + } + _getUnionNames() { + if (!this.length) return [] + const obj = {} + this.forEach(particle => { + if (!particle.length) return undefined + particle.forEach(particle => { + obj[particle.firstWord] = 1 + }) + }) + return Object.keys(obj) + } + getAncestorParticlesByInheritanceViaExtendsKeyword(key) { + const ancestorParticles = this._getAncestorParticles( + (particle, id) => particle._getParticlesByColumn(0, id), + particle => particle.get(key), + this + ) + ancestorParticles.push(this) + return ancestorParticles + } + // Note: as you can probably tell by the name of this method, I don't recommend using this as it will likely be replaced by something better. + getAncestorParticlesByInheritanceViaColumnIndices(thisColumnNumber, extendsColumnNumber) { + const ancestorParticles = this._getAncestorParticles( + (particle, id) => particle._getParticlesByColumn(thisColumnNumber, id), + particle => particle.getWord(extendsColumnNumber), + this + ) + ancestorParticles.push(this) + return ancestorParticles + } + _getAncestorParticles(getPotentialParentParticlesByIdFn, getParentIdFn, cannotContainParticle) { + const parentId = getParentIdFn(this) + if (!parentId) return [] + const potentialParentParticles = getPotentialParentParticlesByIdFn(this.parent, parentId) + if (!potentialParentParticles.length) throw new Error(`"${this.getLine()} tried to extend "${parentId}" but "${parentId}" not found.`) + if (potentialParentParticles.length > 1) throw new Error(`Invalid inheritance paths. Multiple unique ids found for "${parentId}"`) + const parentParticle = potentialParentParticles[0] + // todo: detect loops + if (parentParticle === cannotContainParticle) throw new Error(`Loop detected between '${this.getLine()}' and '${parentParticle.getLine()}'`) + const ancestorParticles = parentParticle._getAncestorParticles(getPotentialParentParticlesByIdFn, getParentIdFn, cannotContainParticle) + ancestorParticles.push(parentParticle) + return ancestorParticles + } + pathVectorToFirstWordPath(pathVector) { + const path = pathVector.slice() // copy array + const names = [] + let particle = this + while (path.length) { + if (!particle) return names + names.push(particle.particleAt(path[0]).firstWord) + particle = particle.particleAt(path.shift()) + } + return names + } + toStringWithLineNumbers() { + return this.toString() + .split("\n") + .map((line, index) => `${index + 1} ${line}`) + .join("\n") + } + get asCsv() { + return this.toDelimited(",") + } + _getTypes(header) { + const matrix = this._getMatrix(header) + const types = header.map(i => "int") + matrix.forEach(row => { + row.forEach((value, index) => { + const type = types[index] + if (type === "string") return 1 + if (value === undefined || value === "") return 1 + if (type === "float") { + if (value.match(/^\-?[0-9]*\.?[0-9]*$/)) return 1 + types[index] = "string" + } + if (value.match(/^\-?[0-9]+$/)) return 1 + types[index] = "string" + }) + }) + return types + } + toDataTable(header = this._getUnionNames()) { + const types = this._getTypes(header) + const parsers = { + string: str => str, + float: parseFloat, + int: parseInt + } + const cellFn = (cellValue, rowIndex, columnIndex) => (rowIndex ? parsers[types[columnIndex]](cellValue) : cellValue) + const arrays = this._toArrays(header, cellFn) + arrays.rows.unshift(arrays.header) + return arrays.rows + } + toDelimited(delimiter, header = this._getUnionNames(), escapeSpecialChars = true) { + const regex = new RegExp(`(\\n|\\"|\\${delimiter})`) + const cellFn = (str, row, column) => (!str.toString().match(regex) ? str : `"` + str.replace(/\"/g, `""`) + `"`) + return this._toDelimited(delimiter, header, escapeSpecialChars ? cellFn : str => str) + } + _getMatrix(columns) { + const matrix = [] + this.forEach(child => { + const row = [] + columns.forEach(col => { + row.push(child.get(col)) + }) + matrix.push(row) + }) + return matrix + } + _toArrays(columnNames, cellFn) { + const skipHeaderRow = 1 + const header = columnNames.map((columnName, index) => cellFn(columnName, 0, index)) + const rows = this.map((particle, rowNumber) => + columnNames.map((columnName, columnIndex) => { + const childParticle = particle.getParticle(columnName) + const content = childParticle ? childParticle.contentWithChildren : "" + return cellFn(content, rowNumber + skipHeaderRow, columnIndex) + }) + ) + return { + rows, + header + } + } + _toDelimited(delimiter, header, cellFn) { + const data = this._toArrays(header, cellFn) + return data.header.join(delimiter) + "\n" + data.rows.map(row => row.join(delimiter)).join("\n") + } + get asTable() { + // Output a table for printing + return this._toTable(100, false) + } + toFormattedTable(maxCharactersPerColumn, alignRight = false) { + return this._toTable(maxCharactersPerColumn, alignRight) + } + _toTable(maxCharactersPerColumn, alignRight = false) { + const header = this._getUnionNames() + // Set initial column widths + const widths = header.map(col => (col.length > maxCharactersPerColumn ? maxCharactersPerColumn : col.length)) + // Expand column widths if needed + this.forEach(particle => { + if (!particle.length) return true + header.forEach((col, index) => { + const cellValue = particle.get(col) + if (!cellValue) return true + const length = cellValue.toString().length + if (length > widths[index]) widths[index] = length > maxCharactersPerColumn ? maxCharactersPerColumn : length + }) + }) + const cellFn = (cellText, row, col) => { + const width = widths[col] + // Strip newlines in fixedWidth output + const cellValue = cellText.toString().replace(/\n/g, "\\n") + const cellLength = cellValue.length + if (cellLength > width) return cellValue.substr(0, width) + "..." + const padding = " ".repeat(width - cellLength) + return alignRight ? padding + cellValue : cellValue + padding + } + return this._toDelimited(" ", header, cellFn) + } + get asSsv() { + return this.toDelimited(" ") + } + get asOutline() { + return this._toOutline(particle => particle.getLine()) + } + toMappedOutline(particleFn) { + return this._toOutline(particleFn) + } + // Adapted from: https://github.com/notatestuser/treeify.js + _toOutline(particleFn) { + const growBranch = (outlineParticle, last, lastStates, particleFn, callback) => { + let lastStatesCopy = lastStates.slice(0) + const particle = outlineParticle.particle + if (lastStatesCopy.push([outlineParticle, last]) && lastStates.length > 0) { + let line = "" + // firstWordd on the "was last element" states of whatever we're nested within, + // we need to append either blankness or a branch to our line + lastStates.forEach((lastState, idx) => { + if (idx > 0) line += lastState[1] ? " " : "│" + }) + // the prefix varies firstWordd on whether the key contains something to show and + // whether we're dealing with the last element in this collection + // the extra "-" just makes things stand out more. + line += (last ? "└" : "├") + particleFn(particle) + callback(line) + } + if (!particle) return + const length = particle.length + let index = 0 + particle.forEach(particle => { + let lastKey = ++index === length + growBranch({ particle: particle }, lastKey, lastStatesCopy, particleFn, callback) + }) + } + let output = "" + growBranch({ particle: this }, false, [], particleFn, line => (output += line + "\n")) + return output + } + copyTo(particle, index) { + return particle._insertLineAndChildren(this.getLine(), this.childrenToString(), index) + } + // Note: Splits using a positive lookahead + // this.split("foo").join("\n") === this.toString() + split(firstWord) { + const constructor = this.constructor + const ParticleBreakSymbol = this.particleBreakSymbol + const WordBreakSymbol = this.wordBreakSymbol + // todo: cleanup. the escaping is wierd. + return this.toString() + .split(new RegExp(`\\${ParticleBreakSymbol}(?=${firstWord}(?:${WordBreakSymbol}|\\${ParticleBreakSymbol}))`, "g")) + .map(str => new constructor(str)) + } + get asMarkdownTable() { + return this.toMarkdownTableAdvanced(this._getUnionNames(), val => val) + } + toMarkdownTableAdvanced(columns, formatFn) { + const matrix = this._getMatrix(columns) + const empty = columns.map(col => "-") + matrix.unshift(empty) + matrix.unshift(columns) + const lines = matrix.map((row, rowIndex) => { + const formattedValues = row.map((val, colIndex) => formatFn(val, rowIndex, colIndex)) + return `|${formattedValues.join("|")}|` + }) + return lines.join("\n") + } + get asTsv() { + return this.toDelimited("\t") + } + get particleBreakSymbol() { + return TN_NODE_BREAK_SYMBOL + } + get wordBreakSymbol() { + return TN_WORD_BREAK_SYMBOL + } + get edgeSymbolRegex() { + return new RegExp(this.edgeSymbol, "g") + } + get particleBreakSymbolRegex() { + return new RegExp(this.particleBreakSymbol, "g") + } + get edgeSymbol() { + return TN_EDGE_SYMBOL + } + _textToContentAndChildrenTuple(text) { + const lines = text.split(this.particleBreakSymbolRegex) + const firstLine = lines.shift() + const children = !lines.length + ? undefined + : lines + .map(line => (line.substr(0, 1) === this.edgeSymbol ? line : this.edgeSymbol + line)) + .map(line => line.substr(1)) + .join(this.particleBreakSymbol) + return [firstLine, children] + } + _getLine() { + return this._line + } + _setLine(line = "") { + this._line = line + if (this._words) delete this._words + return this + } + _clearChildren() { + this._deleteByIndexes(Utils.getRange(0, this.length)) + delete this._children + return this + } + _setChildren(content, circularCheckArray) { + this._clearChildren() + if (!content) return this + // set from string + if (typeof content === "string") { + this._appendChildrenFromString(content) + return this + } + // set from particle + if (content instanceof Particle) { + content.forEach(particle => this._insertLineAndChildren(particle.getLine(), particle.childrenToString())) + return this + } + // If we set from object, create an array of inserted objects to avoid circular loops + if (!circularCheckArray) circularCheckArray = [content] + return this._setFromObject(content, circularCheckArray) + } + _setFromObject(content, circularCheckArray) { + for (let firstWord in content) { + if (!content.hasOwnProperty(firstWord)) continue + // Branch the circularCheckArray, as we only have same branch circular arrays + this._appendFromJavascriptObjectTuple(firstWord, content[firstWord], circularCheckArray.slice(0)) + } + return this + } + // todo: refactor the below. + _appendFromJavascriptObjectTuple(firstWord, content, circularCheckArray) { + const type = typeof content + let line + let children + if (content === null) line = firstWord + " " + null + else if (content === undefined) line = firstWord + else if (type === "string") { + const tuple = this._textToContentAndChildrenTuple(content) + line = firstWord + " " + tuple[0] + children = tuple[1] + } else if (type === "function") line = firstWord + " " + content.toString() + else if (type !== "object") line = firstWord + " " + content + else if (content instanceof Date) line = firstWord + " " + content.getTime().toString() + else if (content instanceof Particle) { + line = firstWord + children = new Particle(content.childrenToString(), content.getLine()) + } else if (circularCheckArray.indexOf(content) === -1) { + circularCheckArray.push(content) + line = firstWord + const length = content instanceof Array ? content.length : Object.keys(content).length + if (length) children = new Particle()._setChildren(content, circularCheckArray) + } else { + // iirc this is return early from circular + return + } + this._insertLineAndChildren(line, children) + } + _insertLineAndChildren(line, children, index = this.length) { + const parser = this._getParser()._getParser(line, this) + const newParticle = new parser(children, line, this) + const adjustedIndex = index < 0 ? this.length + index : index + this._getChildrenArray().splice(adjustedIndex, 0, newParticle) + if (this._index) this._makeIndex(adjustedIndex) + this.clearQuickCache() + return newParticle + } + _appendChildrenFromString(str) { + const lines = str.split(this.particleBreakSymbolRegex) + const parentStack = [] + let currentIndentCount = -1 + let lastParticle = this + lines.forEach(line => { + const indentCount = this._getIndentCount(line) + if (indentCount > currentIndentCount) { + currentIndentCount++ + parentStack.push(lastParticle) + } else if (indentCount < currentIndentCount) { + // pop things off stack + while (indentCount < currentIndentCount) { + parentStack.pop() + currentIndentCount-- + } + } + const lineContent = line.substr(currentIndentCount) + const parent = parentStack[parentStack.length - 1] + const parser = parent._getParser()._getParser(lineContent, parent) + lastParticle = new parser(undefined, lineContent, parent) + parent._getChildrenArray().push(lastParticle) + }) + } + _getIndex() { + // StringMap {firstWord: index} + // When there are multiple tails with the same firstWord, _index stores the last content. + // todo: change the above behavior: when a collision occurs, create an array. + return this._index || this._makeIndex() + } + getContentsArray() { + return this.map(particle => particle.content) + } + getChildrenByParser(parser) { + return this.filter(child => child instanceof parser) + } + getAncestorByParser(parser) { + if (this instanceof parser) return this + if (this.isRoot()) return undefined + const parent = this.parent + return parent instanceof parser ? parent : parent.getAncestorByParser(parser) + } + getParticleByParser(parser) { + return this.find(child => child instanceof parser) + } + indexOfLast(firstWord) { + const result = this._getIndex()[firstWord] + return result === undefined ? -1 : result + } + // todo: renmae to indexOfFirst? + indexOf(firstWord) { + if (!this.has(firstWord)) return -1 + const length = this.length + const particles = this._getChildrenArray() + for (let index = 0; index < length; index++) { + if (particles[index].firstWord === firstWord) return index + } + } + // todo: rename this. it is a particular type of object. + toObject() { + return this._toObject() + } + getFirstWords() { + return this.map(particle => particle.firstWord) + } + _makeIndex(startAt = 0) { + if (!this._index || !startAt) this._index = {} + const particles = this._getChildrenArray() + const newIndex = this._index + const length = particles.length + for (let index = startAt; index < length; index++) { + newIndex[particles[index].firstWord] = index + } + return newIndex + } + _childrenToXml(indentCount) { + return this.map(particle => particle._toXml(indentCount)).join("") + } + _getIndentCount(str) { + let level = 0 + const edgeChar = this.edgeSymbol + while (str[level] === edgeChar) { + level++ + } + return level + } + clone(children = this.childrenToString(), line = this.getLine()) { + return new this.constructor(children, line) + } + hasFirstWord(firstWord) { + return this._hasFirstWord(firstWord) + } + has(firstWordPath) { + const edgeSymbol = this.edgeSymbol + if (!firstWordPath.includes(edgeSymbol)) return this.hasFirstWord(firstWordPath) + const parts = firstWordPath.split(edgeSymbol) + const next = this.getParticle(parts.shift()) + if (!next) return false + return next.has(parts.join(edgeSymbol)) + } + hasParticle(particle) { + const needle = particle.toString() + return this.getChildren().some(particle => particle.toString() === needle) + } + _hasFirstWord(firstWord) { + return this._getIndex()[firstWord] !== undefined + } + map(fn) { + return this.getChildren().map(fn) + } + filter(fn = item => item) { + return this.getChildren().filter(fn) + } + find(fn) { + return this.getChildren().find(fn) + } + findLast(fn) { + return this.getChildren().reverse().find(fn) + } + every(fn) { + let index = 0 + for (let particle of this.getTopDownArrayIterator()) { + if (!fn(particle, index)) return false + index++ + } + return true + } + forEach(fn) { + this.getChildren().forEach(fn) + return this + } + // Recurse if predicate passes + deepVisit(predicate) { + this.forEach(particle => { + if (predicate(particle) !== false) particle.deepVisit(predicate) + }) + } + get quickCache() { + if (!this._quickCache) this._quickCache = {} + return this._quickCache + } + getCustomIndex(key) { + if (!this.quickCache.customIndexes) this.quickCache.customIndexes = {} + const customIndexes = this.quickCache.customIndexes + if (customIndexes[key]) return customIndexes[key] + const customIndex = {} + customIndexes[key] = customIndex + this.filter(file => file.has(key)).forEach(file => { + const value = file.get(key) + if (!customIndex[value]) customIndex[value] = [] + customIndex[value].push(file) + }) + return customIndex + } + clearQuickCache() { + delete this._quickCache + } + // todo: protected? + _clearIndex() { + delete this._index + this.clearQuickCache() + } + slice(start, end) { + return this.getChildren().slice(start, end) + } + // todo: make 0 and 1 a param + getInheritanceParticles() { + const paths = {} + const result = new Particle() + this.forEach(particle => { + const key = particle.getWord(0) + const parentKey = particle.getWord(1) + const parentPath = paths[parentKey] + paths[key] = parentPath ? [parentPath, key].join(" ") : key + result.touchParticle(paths[key]) + }) + return result + } + _getGrandParent() { + return this.isRoot() || this.parent.isRoot() ? undefined : this.parent.parent + } + _getParser() { + if (!Particle._parserCombinators.has(this.constructor)) Particle._parserCombinators.set(this.constructor, this.createParserCombinator()) + return Particle._parserCombinators.get(this.constructor) + } + createParserCombinator() { + return new ParserCombinator(this.constructor) + } + static _makeUniqueId() { + if (this._uniqueId === undefined) this._uniqueId = 0 + this._uniqueId++ + return this._uniqueId + } + static _getFileFormat(path) { + const format = path.split(".").pop() + return FileFormat[format] ? format : FileFormat.particles + } + getLineModifiedTime() { + return this._lineModifiedTime || this._particleCreationTime + } + getChildArrayModifiedTime() { + return this._childArrayModifiedTime || this._particleCreationTime + } + _setChildArrayMofifiedTime(value) { + this._childArrayModifiedTime = value + return this + } + getLineOrChildrenModifiedTime() { + return Math.max( + this.getLineModifiedTime(), + this.getChildArrayModifiedTime(), + Math.max.apply( + null, + this.map(child => child.getLineOrChildrenModifiedTime()) + ) + ) + } + _setVirtualParentParticle(particle) { + this._virtualParentParticle = particle + return this + } + _getVirtualParentParticle() { + return this._virtualParentParticle + } + _setVirtualAncestorParticlesByInheritanceViaColumnIndicesAndThenExpand(particles, thisIdColumnNumber, extendsIdColumnNumber) { + const map = {} + for (let particle of particles) { + const particleId = particle.getWord(thisIdColumnNumber) + if (map[particleId]) throw new Error(`Tried to define a particle with id "${particleId}" but one is already defined.`) + map[particleId] = { + particleId: particleId, + particle: particle, + parentId: particle.getWord(extendsIdColumnNumber) + } + } + // Add parent Particles + Object.values(map).forEach(particleInfo => { + const parentId = particleInfo.parentId + const parentParticle = map[parentId] + if (parentId && !parentParticle) throw new Error(`Particle "${particleInfo.particleId}" tried to extend "${parentId}" but "${parentId}" not found.`) + if (parentId) particleInfo.particle._setVirtualParentParticle(parentParticle.particle) + }) + particles.forEach(particle => particle._expandFromVirtualParentParticle()) + return this + } + _expandFromVirtualParentParticle() { + if (this._isVirtualExpanded) return this + this._isExpanding = true + let parentParticle = this._getVirtualParentParticle() + if (parentParticle) { + if (parentParticle._isExpanding) throw new Error(`Loop detected: '${this.getLine()}' is the ancestor of one of its ancestors.`) + parentParticle._expandFromVirtualParentParticle() + const clone = this.clone() + this._setChildren(parentParticle.childrenToString()) + this.extend(clone) + } + this._isExpanding = false + this._isVirtualExpanded = true + } + // todo: solve issue related to whether extend should overwrite or append. + _expandChildren(thisIdColumnNumber, extendsIdColumnNumber, childrenThatNeedExpanding = this.getChildren()) { + return this._setVirtualAncestorParticlesByInheritanceViaColumnIndicesAndThenExpand(childrenThatNeedExpanding, thisIdColumnNumber, extendsIdColumnNumber) + } + // todo: add more testing. + // todo: solve issue with where extend should overwrite or append + // todo: should take a parsers? to decide whether to overwrite or append. + // todo: this is slow. + extend(particleOrStr) { + const particle = particleOrStr instanceof Particle ? particleOrStr : new Particle(particleOrStr) + const usedFirstWords = new Set() + particle.forEach(sourceParticle => { + const firstWord = sourceParticle.firstWord + let targetParticle + const isAnArrayNotMap = usedFirstWords.has(firstWord) + if (!this.has(firstWord)) { + usedFirstWords.add(firstWord) + this.appendLineAndChildren(sourceParticle.getLine(), sourceParticle.childrenToString()) + return true + } + if (isAnArrayNotMap) targetParticle = this.appendLine(sourceParticle.getLine()) + else { + targetParticle = this.touchParticle(firstWord).setContent(sourceParticle.content) + usedFirstWords.add(firstWord) + } + if (sourceParticle.length) targetParticle.extend(sourceParticle) + }) + return this + } + lastParticle() { + return this.getChildren()[this.length - 1] + } + expandLastFromTopMatter() { + const clone = this.clone() + const map = new Map() + const lastParticle = clone.lastParticle() + lastParticle.getOlderSiblings().forEach(particle => map.set(particle.getWord(0), particle)) + lastParticle.topDownArray.forEach(particle => { + const replacement = map.get(particle.getWord(0)) + if (!replacement) return + particle.replaceParticle(str => replacement.toString()) + }) + return lastParticle + } + macroExpand(macroDefinitionWord, macroUsageWord) { + const clone = this.clone() + const defs = clone.findParticles(macroDefinitionWord) + const allUses = clone.findParticles(macroUsageWord) + const wordBreakSymbol = clone.wordBreakSymbol + defs.forEach(def => { + const macroName = def.getWord(1) + const uses = allUses.filter(particle => particle.hasWord(1, macroName)) + const params = def.getWordsFrom(2) + const replaceFn = str => { + const paramValues = str.split(wordBreakSymbol).slice(2) + let newParticle = def.childrenToString() + params.forEach((param, index) => { + newParticle = newParticle.replace(new RegExp(param, "g"), paramValues[index]) + }) + return newParticle + } + uses.forEach(particle => { + particle.replaceParticle(replaceFn) + }) + def.destroy() + }) + return clone + } + setChildren(children) { + return this._setChildren(children) + } + _updateLineModifiedTimeAndTriggerEvent() { + this._lineModifiedTime = this._getProcessTimeInMilliseconds() + } + insertWord(index, word) { + const wi = this.wordBreakSymbol + const words = this._getLine().split(wi) + words.splice(index, 0, word) + this.setLine(words.join(wi)) + return this + } + deleteDuplicates() { + const set = new Set() + this.topDownArray.forEach(particle => { + const str = particle.toString() + if (set.has(str)) particle.destroy() + else set.add(str) + }) + return this + } + setWord(index, word) { + const wi = this.wordBreakSymbol + const words = this._getLine().split(wi) + words[index] = word + this.setLine(words.join(wi)) + return this + } + deleteChildren() { + return this._clearChildren() + } + setContent(content) { + if (content === this.content) return this + const newArray = [this.firstWord] + if (content !== undefined) { + content = content.toString() + if (content.match(this.particleBreakSymbol)) return this.setContentWithChildren(content) + newArray.push(content) + } + this._setLine(newArray.join(this.wordBreakSymbol)) + this._updateLineModifiedTimeAndTriggerEvent() + return this + } + prependSibling(line, children) { + return this.parent.insertLineAndChildren(line, children, this.getIndex()) + } + appendSibling(line, children) { + return this.parent.insertLineAndChildren(line, children, this.getIndex() + 1) + } + setContentWithChildren(text) { + // todo: deprecate + if (!text.includes(this.particleBreakSymbol)) { + this._clearChildren() + return this.setContent(text) + } + const lines = text.split(this.particleBreakSymbolRegex) + const firstLine = lines.shift() + this.setContent(firstLine) + // tood: cleanup. + const remainingString = lines.join(this.particleBreakSymbol) + const children = new Particle(remainingString) + if (!remainingString) children.appendLine("") + this.setChildren(children) + return this + } + setFirstWord(firstWord) { + return this.setWord(0, firstWord) + } + setLine(line) { + if (line === this.getLine()) return this + // todo: clear parent TMTimes + this.parent._clearIndex() + this._setLine(line) + this._updateLineModifiedTimeAndTriggerEvent() + return this + } + duplicate() { + return this.parent._insertLineAndChildren(this.getLine(), this.childrenToString(), this.getIndex() + 1) + } + trim() { + // todo: could do this so only the trimmed rows are deleted. + this.setChildren(this.childrenToString().trim()) + return this + } + destroy() { + this.parent._deleteParticle(this) + } + set(firstWordPath, text) { + return this.touchParticle(firstWordPath).setContentWithChildren(text) + } + setFromText(text) { + if (this.toString() === text) return this + const tuple = this._textToContentAndChildrenTuple(text) + this.setLine(tuple[0]) + return this._setChildren(tuple[1]) + } + setPropertyIfMissing(prop, value) { + if (this.has(prop)) return true + return this.touchParticle(prop).setContent(value) + } + setProperties(propMap) { + const props = Object.keys(propMap) + const values = Object.values(propMap) + // todo: is there a built in particle method to do this? + props.forEach((prop, index) => { + const value = values[index] + if (!value) return true + if (this.get(prop) === value) return true + this.touchParticle(prop).setContent(value) + }) + return this + } + // todo: throw error if line contains a \n + appendLine(line) { + return this._insertLineAndChildren(line) + } + appendUniqueLine(line) { + if (!this.hasLine(line)) return this.appendLine(line) + return this.findLine(line) + } + appendLineAndChildren(line, children) { + return this._insertLineAndChildren(line, children) + } + getParticlesByRegex(regex) { + const matches = [] + regex = regex instanceof RegExp ? [regex] : regex + this._getParticlesByLineRegex(matches, regex) + return matches + } + // todo: remove? + getParticlesByLinePrefixes(columns) { + const matches = [] + this._getParticlesByLineRegex( + matches, + columns.map(str => new RegExp("^" + str)) + ) + return matches + } + particlesThatStartWith(prefix) { + return this.filter(particle => particle.getLine().startsWith(prefix)) + } + _getParticlesByLineRegex(matches, regs) { + const rgs = regs.slice(0) + const reg = rgs.shift() + const candidates = this.filter(child => child.getLine().match(reg)) + if (!rgs.length) return candidates.forEach(cand => matches.push(cand)) + candidates.forEach(cand => cand._getParticlesByLineRegex(matches, rgs)) + } + concat(particle) { + if (typeof particle === "string") particle = new Particle(particle) + return particle.map(particle => this._insertLineAndChildren(particle.getLine(), particle.childrenToString())) + } + _deleteByIndexes(indexesToDelete) { + if (!indexesToDelete.length) return this + this._clearIndex() + // note: assumes indexesToDelete is in ascending order + const deletedParticles = indexesToDelete.reverse().map(index => this._getChildrenArray().splice(index, 1)[0]) + this._setChildArrayMofifiedTime(this._getProcessTimeInMilliseconds()) + return this + } + _deleteParticle(particle) { + const index = this._indexOfParticle(particle) + return index > -1 ? this._deleteByIndexes([index]) : 0 + } + reverse() { + this._clearIndex() + this._getChildrenArray().reverse() + return this + } + shift() { + if (!this.length) return null + const particle = this._getChildrenArray().shift() + return particle.copyTo(new this.constructor(), 0) + } + sort(fn) { + this._getChildrenArray().sort(fn) + this._clearIndex() + return this + } + invert() { + this.forEach(particle => particle.words.reverse()) + return this + } + _rename(oldFirstWord, newFirstWord) { + const index = this.indexOf(oldFirstWord) + if (index === -1) return this + const particle = this._getChildrenArray()[index] + particle.setFirstWord(newFirstWord) + this._clearIndex() + return this + } + // Does not recurse. + remap(map) { + this.forEach(particle => { + const firstWord = particle.firstWord + if (map[firstWord] !== undefined) particle.setFirstWord(map[firstWord]) + }) + return this + } + rename(oldFirstWord, newFirstWord) { + this._rename(oldFirstWord, newFirstWord) + return this + } + renameAll(oldName, newName) { + this.findParticles(oldName).forEach(particle => particle.setFirstWord(newName)) + return this + } + _deleteAllChildParticlesWithFirstWord(firstWord) { + if (!this.has(firstWord)) return this + const allParticles = this._getChildrenArray() + const indexesToDelete = [] + allParticles.forEach((particle, index) => { + if (particle.firstWord === firstWord) indexesToDelete.push(index) + }) + return this._deleteByIndexes(indexesToDelete) + } + delete(path = "") { + const edgeSymbol = this.edgeSymbol + if (!path.includes(edgeSymbol)) return this._deleteAllChildParticlesWithFirstWord(path) + const parts = path.split(edgeSymbol) + const nextFirstWord = parts.pop() + const targetParticle = this.getParticle(parts.join(edgeSymbol)) + return targetParticle ? targetParticle._deleteAllChildParticlesWithFirstWord(nextFirstWord) : 0 + } + deleteColumn(firstWord = "") { + this.forEach(particle => particle.delete(firstWord)) + return this + } + _getNonMaps() { + const results = this.topDownArray.filter(particle => particle.hasDuplicateFirstWords()) + if (this.hasDuplicateFirstWords()) results.unshift(this) + return results + } + replaceParticle(fn) { + const parent = this.parent + const index = this.getIndex() + const newParticles = new Particle(fn(this.toString())) + const returnedParticles = [] + newParticles.forEach((child, childIndex) => { + const newParticle = parent.insertLineAndChildren(child.getLine(), child.childrenToString(), index + childIndex) + returnedParticles.push(newParticle) + }) + this.destroy() + return returnedParticles + } + insertLineAndChildren(line, children, index) { + return this._insertLineAndChildren(line, children, index) + } + insertLine(line, index) { + return this._insertLineAndChildren(line, undefined, index) + } + prependLine(line) { + return this.insertLine(line, 0) + } + pushContentAndChildren(content, children) { + let index = this.length + while (this.has(index.toString())) { + index++ + } + const line = index.toString() + (content === undefined ? "" : this.wordBreakSymbol + content) + return this.appendLineAndChildren(line, children) + } + deleteBlanks() { + this.getChildren() + .filter(particle => particle.isBlankLine()) + .forEach(particle => particle.destroy()) + return this + } + // todo: add "globalReplace" method? Which runs a global regex or string replace on the Particle as a string? + firstWordSort(firstWordOrder) { + return this._firstWordSort(firstWordOrder) + } + deleteWordAt(wordIndex) { + const words = this.words + words.splice(wordIndex, 1) + return this.setWords(words) + } + trigger(event) { + if (this._listeners && this._listeners.has(event.constructor)) { + const listeners = this._listeners.get(event.constructor) + const listenersToRemove = [] + for (let index = 0; index < listeners.length; index++) { + const listener = listeners[index] + if (listener(event) === true) listenersToRemove.push(index) + } + listenersToRemove.reverse().forEach(index => listenersToRemove.splice(index, 1)) + } + } + triggerAncestors(event) { + if (this.isRoot()) return + const parent = this.parent + parent.trigger(event) + parent.triggerAncestors(event) + } + onLineChanged(eventHandler) { + return this._addEventListener(LineChangedParticleEvent, eventHandler) + } + onDescendantChanged(eventHandler) { + return this._addEventListener(DescendantChangedParticleEvent, eventHandler) + } + onChildAdded(eventHandler) { + return this._addEventListener(ChildAddedParticleEvent, eventHandler) + } + onChildRemoved(eventHandler) { + return this._addEventListener(ChildRemovedParticleEvent, eventHandler) + } + _addEventListener(eventClass, eventHandler) { + if (!this._listeners) this._listeners = new Map() + if (!this._listeners.has(eventClass)) this._listeners.set(eventClass, []) + this._listeners.get(eventClass).push(eventHandler) + return this + } + setWords(words) { + return this.setLine(words.join(this.wordBreakSymbol)) + } + setWordsFrom(index, words) { + this.setWords(this.words.slice(0, index).concat(words)) + return this + } + appendWord(word) { + const words = this.words + words.push(word) + return this.setWords(words) + } + _firstWordSort(firstWordOrder, secondarySortFn) { + const particleAFirst = -1 + const particleBFirst = 1 + const map = {} + firstWordOrder.forEach((word, index) => { + map[word] = index + }) + this.sort((particleA, particleB) => { + const valA = map[particleA.firstWord] + const valB = map[particleB.firstWord] + if (valA > valB) return particleBFirst + if (valA < valB) return particleAFirst + return secondarySortFn ? secondarySortFn(particleA, particleB) : 0 + }) + return this + } + _touchParticle(firstWordPathArray) { + let contextParticle = this + firstWordPathArray.forEach(firstWord => { + contextParticle = contextParticle.getParticle(firstWord) || contextParticle.appendLine(firstWord) + }) + return contextParticle + } + _touchParticleByString(str) { + str = str.replace(this.particleBreakSymbolRegex, "") // todo: do we want to do this sanitization? + return this._touchParticle(str.split(this.wordBreakSymbol)) + } + touchParticle(str) { + return this._touchParticleByString(str) + } + appendParticle(particle) { + return this.appendLineAndChildren(particle.getLine(), particle.childrenToString()) + } + hasLine(line) { + return this.getChildren().some(particle => particle.getLine() === line) + } + findLine(line) { + return this.getChildren().find(particle => particle.getLine() === line) + } + getParticlesByLine(line) { + return this.filter(particle => particle.getLine() === line) + } + toggleLine(line) { + const lines = this.getParticlesByLine(line) + if (lines.length) { + lines.map(line => line.destroy()) + return this + } + return this.appendLine(line) + } + // todo: remove? + sortByColumns(indexOrIndices) { + const indices = indexOrIndices instanceof Array ? indexOrIndices : [indexOrIndices] + const length = indices.length + this.sort((particleA, particleB) => { + const wordsA = particleA.words + const wordsB = particleB.words + for (let index = 0; index < length; index++) { + const col = indices[index] + const av = wordsA[col] + const bv = wordsB[col] + if (av === undefined) return -1 + if (bv === undefined) return 1 + if (av > bv) return 1 + else if (av < bv) return -1 + } + return 0 + }) + return this + } + getWordsAsSet() { + return new Set(this.getWordsFrom(1)) + } + appendWordIfMissing(word) { + if (this.getWordsAsSet().has(word)) return this + return this.appendWord(word) + } + // todo: check to ensure identical objects + addObjectsAsDelimited(arrayOfObjects, delimiter = Utils._chooseDelimiter(new Particle(arrayOfObjects).toString())) { + const header = Object.keys(arrayOfObjects[0]) + .join(delimiter) + .replace(/[\n\r]/g, "") + const rows = arrayOfObjects.map(item => + Object.values(item) + .join(delimiter) + .replace(/[\n\r]/g, "") + ) + return this.addUniqueRowsToNestedDelimited(header, rows) + } + setChildrenAsDelimited(particle, delimiter = Utils._chooseDelimiter(particle.toString())) { + particle = particle instanceof Particle ? particle : new Particle(particle) + return this.setChildren(particle.toDelimited(delimiter)) + } + convertChildrenToDelimited(delimiter = Utils._chooseDelimiter(this.childrenToString())) { + // todo: handle newlines!!! + return this.setChildren(this.toDelimited(delimiter)) + } + addUniqueRowsToNestedDelimited(header, rowsAsStrings) { + if (!this.length) this.appendLine(header) + // todo: this looks brittle + rowsAsStrings.forEach(row => { + if (!this.toString().includes(row)) this.appendLine(row) + }) + return this + } + shiftLeft() { + const grandParent = this._getGrandParent() + if (!grandParent) return this + const parentIndex = this.parent.getIndex() + const newParticle = grandParent.insertLineAndChildren(this.getLine(), this.length ? this.childrenToString() : undefined, parentIndex + 1) + this.destroy() + return newParticle + } + pasteText(text) { + const parent = this.parent + const index = this.getIndex() + const newParticles = new Particle(text) + const firstParticle = newParticles.particleAt(0) + if (firstParticle) { + this.setLine(firstParticle.getLine()) + if (firstParticle.length) this.setChildren(firstParticle.childrenToString()) + } else { + this.setLine("") + } + newParticles.forEach((child, childIndex) => { + if (!childIndex) + // skip first + return true + parent.insertLineAndChildren(child.getLine(), child.childrenToString(), index + childIndex) + }) + return this + } + templateToString(obj) { + // todo: compile/cache for perf? + const particle = this.clone() + particle.topDownArray.forEach(particle => { + const line = particle.getLine().replace(/{([^\}]+)}/g, (match, path) => { + const replacement = obj[path] + if (replacement === undefined) throw new Error(`In string template no match found on line "${particle.getLine()}"`) + return replacement + }) + particle.pasteText(line) + }) + return particle.toString() + } + shiftRight() { + const olderSibling = this._getClosestOlderSibling() + if (!olderSibling) return this + const newParticle = olderSibling.appendLineAndChildren(this.getLine(), this.length ? this.childrenToString() : undefined) + this.destroy() + return newParticle + } + shiftYoungerSibsRight() { + const particles = this.getYoungerSiblings() + particles.forEach(particle => particle.shiftRight()) + return this + } + sortBy(nameOrNames) { + const names = nameOrNames instanceof Array ? nameOrNames : [nameOrNames] + const length = names.length + this.sort((particleA, particleB) => { + if (!particleB.length && !particleA.length) return 0 + else if (!particleA.length) return -1 + else if (!particleB.length) return 1 + for (let index = 0; index < length; index++) { + const firstWord = names[index] + const av = particleA.get(firstWord) + const bv = particleB.get(firstWord) + if (av > bv) return 1 + else if (av < bv) return -1 + } + return 0 + }) + return this + } + selectParticle() { + this._selected = true + } + unselectParticle() { + delete this._selected + } + isSelected() { + return !!this._selected + } + async saveVersion() { + const newVersion = this.toString() + const topUndoVersion = this._getTopUndoVersion() + if (newVersion === topUndoVersion) return undefined + this._recordChange(newVersion) + this._setSavedVersion(this.toString()) + return this + } + hasUnsavedChanges() { + return this.toString() !== this._getSavedVersion() + } + async redo() { + const undoStack = this._getUndoStack() + const redoStack = this._getRedoStack() + if (!redoStack.length) return undefined + undoStack.push(redoStack.pop()) + return this._reloadFromUndoTop() + } + async undo() { + const undoStack = this._getUndoStack() + const redoStack = this._getRedoStack() + if (undoStack.length === 1) return undefined + redoStack.push(undoStack.pop()) + return this._reloadFromUndoTop() + } + _getSavedVersion() { + return this._savedVersion + } + _setSavedVersion(str) { + this._savedVersion = str + return this + } + _clearRedoStack() { + const redoStack = this._getRedoStack() + redoStack.splice(0, redoStack.length) + } + getChangeHistory() { + return this._getUndoStack().slice(0) + } + _getUndoStack() { + if (!this._undoStack) this._undoStack = [] + return this._undoStack + } + _getRedoStack() { + if (!this._redoStack) this._redoStack = [] + return this._redoStack + } + _getTopUndoVersion() { + const undoStack = this._getUndoStack() + return undoStack[undoStack.length - 1] + } + async _reloadFromUndoTop() { + this.setChildren(this._getTopUndoVersion()) + } + _recordChange(newVersion) { + this._clearRedoStack() + this._getUndoStack().push(newVersion) // todo: use diffs? + } + static fromCsv(str) { + return this.fromDelimited(str, ",", '"') + } + // todo: jeez i think we can come up with a better name than "JsonSubset" + static fromJsonSubset(str) { + return new Particle(JSON.parse(str)) + } + static serializedParticleToParticle(particle) { + const language = new Particle() + const cellDelimiter = language.wordBreakSymbol + const particleDelimiter = language.particleBreakSymbol + const line = particle.cells ? particle.cells.join(cellDelimiter) : undefined + const newParticle = new Particle(undefined, line) + if (particle.children) + particle.children.forEach(child => { + newParticle.appendParticle(this.serializedParticleToParticle(child)) + }) + return newParticle + } + static fromJson(str) { + return this.serializedParticleToParticle(JSON.parse(str)) + } + static fromGridJson(str) { + const lines = JSON.parse(str) + const language = new Particle() + const cellDelimiter = language.wordBreakSymbol + const particleDelimiter = language.particleBreakSymbol + return new Particle(lines.map(line => line.join(cellDelimiter)).join(particleDelimiter)) + } + static fromSsv(str) { + return this.fromDelimited(str, " ", '"') + } + static fromTsv(str) { + return this.fromDelimited(str, "\t", '"') + } + static fromDelimited(str, delimiter, quoteChar = '"') { + str = str.replace(/\r/g, "") // remove windows newlines if present + const rows = this._getEscapedRows(str, delimiter, quoteChar) + return this._rowsToParticle(rows, delimiter, true) + } + static _getEscapedRows(str, delimiter, quoteChar) { + return str.includes(quoteChar) ? this._strToRows(str, delimiter, quoteChar) : str.split("\n").map(line => line.split(delimiter)) + } + static fromDelimitedNoHeaders(str, delimiter, quoteChar) { + str = str.replace(/\r/g, "") // remove windows newlines if present + const rows = this._getEscapedRows(str, delimiter, quoteChar) + return this._rowsToParticle(rows, delimiter, false) + } + static _strToRows(str, delimiter, quoteChar, newLineChar = "\n") { + const rows = [[]] + const newLine = "\n" + const length = str.length + let currentCell = "" + let inQuote = str.substr(0, 1) === quoteChar + let currentPosition = inQuote ? 1 : 0 + let nextChar + let isLastChar + let currentRow = 0 + let char + let isNextCharAQuote + while (currentPosition < length) { + char = str[currentPosition] + isLastChar = currentPosition + 1 === length + nextChar = str[currentPosition + 1] + isNextCharAQuote = nextChar === quoteChar + if (inQuote) { + if (char !== quoteChar) currentCell += char + else if (isNextCharAQuote) { + // Both the current and next char are ", so the " is escaped + currentCell += nextChar + currentPosition++ // Jump 2 + } else { + // If the current char is a " and the next char is not, it's the end of the quotes + inQuote = false + if (isLastChar) rows[currentRow].push(currentCell) + } + } else { + if (char === delimiter) { + rows[currentRow].push(currentCell) + currentCell = "" + if (isNextCharAQuote) { + inQuote = true + currentPosition++ // Jump 2 + } + } else if (char === newLine) { + rows[currentRow].push(currentCell) + currentCell = "" + currentRow++ + if (nextChar) rows[currentRow] = [] + if (isNextCharAQuote) { + inQuote = true + currentPosition++ // Jump 2 + } + } else if (isLastChar) rows[currentRow].push(currentCell + char) + else currentCell += char + } + currentPosition++ + } + return rows + } + static multiply(particleA, particleB) { + const productParticle = particleA.clone() + productParticle.forEach((particle, index) => { + particle.setChildren(particle.length ? this.multiply(particle, particleB) : particleB.clone()) + }) + return productParticle + } + // Given an array return a particle + static _rowsToParticle(rows, delimiter, hasHeaders) { + const numberOfColumns = rows[0].length + const particle = new Particle() + const names = this._getHeader(rows, hasHeaders) + const rowCount = rows.length + for (let rowIndex = hasHeaders ? 1 : 0; rowIndex < rowCount; rowIndex++) { + let row = rows[rowIndex] + // If the row contains too many columns, shift the extra columns onto the last one. + // This allows you to not have to escape delimiter characters in the final column. + if (row.length > numberOfColumns) { + row[numberOfColumns - 1] = row.slice(numberOfColumns - 1).join(delimiter) + row = row.slice(0, numberOfColumns) + } else if (row.length < numberOfColumns) { + // If the row is missing columns add empty columns until it is full. + // This allows you to make including delimiters for empty ending columns in each row optional. + while (row.length < numberOfColumns) { + row.push("") + } + } + const obj = {} + row.forEach((cellValue, index) => { + obj[names[index]] = cellValue + }) + particle.pushContentAndChildren(undefined, obj) + } + return particle + } + static _initializeXmlParser() { + if (this._xmlParser) return + const windowObj = window + if (typeof windowObj.DOMParser !== "undefined") this._xmlParser = xmlStr => new windowObj.DOMParser().parseFromString(xmlStr, "text/xml") + else if (typeof windowObj.ActiveXObject !== "undefined" && new windowObj.ActiveXObject("Microsoft.XMLDOM")) { + this._xmlParser = xmlStr => { + const xmlDoc = new windowObj.ActiveXObject("Microsoft.XMLDOM") + xmlDoc.async = "false" + xmlDoc.loadXML(xmlStr) + return xmlDoc + } + } else throw new Error("No XML parser found") + } + static fromXml(str) { + this._initializeXmlParser() + const xml = this._xmlParser(str) + try { + return this._particleFromXml(xml).getParticle("children") + } catch (err) { + return this._particleFromXml(this._parseXml2(str)).getParticle("children") + } + } + static _zipObject(keys, values) { + const obj = {} + keys.forEach((key, index) => (obj[key] = values[index])) + return obj + } + static fromShape(shapeArr, rootParticle = new Particle()) { + const part = shapeArr.shift() + if (part !== undefined) { + for (let index = 0; index < part; index++) { + rootParticle.appendLine(index.toString()) + } + } + if (shapeArr.length) rootParticle.forEach(particle => Particle.fromShape(shapeArr.slice(0), particle)) + return rootParticle + } + static fromDataTable(table) { + const header = table.shift() + return new Particle(table.map(row => this._zipObject(header, row))) + } + static _parseXml2(str) { + const el = document.createElement("div") + el.innerHTML = str + return el + } + // todo: cleanup typings + static _particleFromXml(xml) { + const result = new Particle() + const children = new Particle() + // Set attributes + if (xml.attributes) { + for (let index = 0; index < xml.attributes.length; index++) { + result.set(xml.attributes[index].name, xml.attributes[index].value) + } + } + if (xml.data) children.pushContentAndChildren(xml.data) + // Set content + if (xml.childNodes && xml.childNodes.length > 0) { + for (let index = 0; index < xml.childNodes.length; index++) { + const child = xml.childNodes[index] + if (child.tagName && child.tagName.match(/parsererror/i)) throw new Error("Parse Error") + if (child.childNodes.length > 0 && child.tagName) children.appendLineAndChildren(child.tagName, this._particleFromXml(child)) + else if (child.tagName) children.appendLine(child.tagName) + else if (child.data) { + const data = child.data.trim() + if (data) children.pushContentAndChildren(data) + } + } + } + if (children.length > 0) result.touchParticle("children").setChildren(children) + return result + } + static _getHeader(rows, hasHeaders) { + const numberOfColumns = rows[0].length + const headerRow = hasHeaders ? rows[0] : [] + const WordBreakSymbol = " " + const ziRegex = new RegExp(WordBreakSymbol, "g") + if (hasHeaders) { + // Strip any WordBreakSymbols from column names in the header row. + // This makes the mapping not quite 1 to 1 if there are any WordBreakSymbols in names. + for (let index = 0; index < numberOfColumns; index++) { + headerRow[index] = headerRow[index].replace(ziRegex, "") + } + } else { + // If str has no headers, create them as 0,1,2,3 + for (let index = 0; index < numberOfColumns; index++) { + headerRow.push(index.toString()) + } + } + return headerRow + } + static nest(str, xValue) { + const ParticleBreakSymbol = TN_NODE_BREAK_SYMBOL + const WordBreakSymbol = TN_WORD_BREAK_SYMBOL + const indent = ParticleBreakSymbol + WordBreakSymbol.repeat(xValue) + return str ? indent + str.replace(/\n/g, indent) : "" + } + static fromDisk(path) { + const format = this._getFileFormat(path) + const content = require("fs").readFileSync(path, "utf8") + const methods = { + particles: content => new Particle(content), + csv: content => this.fromCsv(content), + tsv: content => this.fromTsv(content) + } + if (!methods[format]) throw new Error(`No support for '${format}'`) + return methods[format](content) + } + static fromFolder(folderPath, filepathPredicate = filepath => filepath !== ".DS_Store") { + const path = require("path") + const fs = require("fs") + const particle = new Particle() + const files = fs + .readdirSync(folderPath) + .map(filename => path.join(folderPath, filename)) + .filter(filepath => !fs.statSync(filepath).isDirectory() && filepathPredicate(filepath)) + .forEach(filePath => particle.appendLineAndChildren(filePath, fs.readFileSync(filePath, "utf8"))) + return particle + } +} +Particle._parserCombinators = new Map() +Particle.ParserCombinator = ParserCombinator +Particle.iris = `sepal_length,sepal_width,petal_length,petal_width,species +6.1,3,4.9,1.8,virginica +5.6,2.7,4.2,1.3,versicolor +5.6,2.8,4.9,2,virginica +6.2,2.8,4.8,1.8,virginica +7.7,3.8,6.7,2.2,virginica +5.3,3.7,1.5,0.2,setosa +6.2,3.4,5.4,2.3,virginica +4.9,2.5,4.5,1.7,virginica +5.1,3.5,1.4,0.2,setosa +5,3.4,1.5,0.2,setosa` +Particle.getVersion = () => "85.0.0" +class AbstractExtendibleParticle extends Particle { + _getFromExtended(firstWordPath) { + const hit = this._getParticleFromExtended(firstWordPath) + return hit ? hit.get(firstWordPath) : undefined + } + _getLineage() { + const newParticle = new Particle() + this.forEach(particle => { + const path = particle._getAncestorsArray().map(particle => particle.id) + path.reverse() + newParticle.touchParticle(path.join(TN_EDGE_SYMBOL)) + }) + return newParticle + } + // todo: be more specific with the param + _getChildrenByParserInExtended(parser) { + return Utils.flatten(this._getAncestorsArray().map(particle => particle.getChildrenByParser(parser))) + } + _getExtendedParent() { + return this._getAncestorsArray()[1] + } + _hasFromExtended(firstWordPath) { + return !!this._getParticleFromExtended(firstWordPath) + } + _getParticleFromExtended(firstWordPath) { + return this._getAncestorsArray().find(particle => particle.has(firstWordPath)) + } + _getConcatBlockStringFromExtended(firstWordPath) { + return this._getAncestorsArray() + .filter(particle => particle.has(firstWordPath)) + .map(particle => particle.getParticle(firstWordPath).childrenToString()) + .reverse() + .join("\n") + } + _doesExtend(parserId) { + return this._getAncestorSet().has(parserId) + } + _getAncestorSet() { + if (!this._cache_ancestorSet) this._cache_ancestorSet = new Set(this._getAncestorsArray().map(def => def.id)) + return this._cache_ancestorSet + } + // Note: the order is: [this, parent, grandParent, ...] + _getAncestorsArray(cannotContainParticles) { + this._initAncestorsArrayCache(cannotContainParticles) + return this._cache_ancestorsArray + } + get idThatThisExtends() { + return this.get(ParticlesConstants.extends) + } + _initAncestorsArrayCache(cannotContainParticles) { + if (this._cache_ancestorsArray) return undefined + if (cannotContainParticles && cannotContainParticles.includes(this)) throw new Error(`Loop detected: '${this.getLine()}' is the ancestor of one of its ancestors.`) + cannotContainParticles = cannotContainParticles || [this] + let ancestors = [this] + const extendedId = this.idThatThisExtends + if (extendedId) { + const parentParticle = this.idToParticleMap[extendedId] + if (!parentParticle) throw new Error(`${extendedId} not found`) + ancestors = ancestors.concat(parentParticle._getAncestorsArray(cannotContainParticles)) + } + this._cache_ancestorsArray = ancestors + } +} +class ExtendibleParticle extends AbstractExtendibleParticle { + get idToParticleMap() { + if (!this.isRoot()) return this.root.idToParticleMap + if (!this._particleMapCache) { + this._particleMapCache = {} + this.forEach(child => { + this._particleMapCache[child.id] = child + }) + } + return this._particleMapCache + } + get id() { + return this.getWord(0) + } +} +window.Particle = Particle +window.ExtendibleParticle = ExtendibleParticle +window.AbstractExtendibleParticle = AbstractExtendibleParticle +window.ParticleEvents = ParticleEvents +window.ParticleWord = ParticleWord + + +// Compiled language parsers will include these files: +const GlobalNamespaceAdditions = { + Utils: "Utils.js", + Particle: "Particle.js", + HandParsersProgram: "Parsers.js", + ParserBackedParticle: "Parsers.js" +} +var ParsersConstantsCompiler +;(function (ParsersConstantsCompiler) { + ParsersConstantsCompiler["stringTemplate"] = "stringTemplate" + ParsersConstantsCompiler["indentCharacter"] = "indentCharacter" + ParsersConstantsCompiler["catchAllCellDelimiter"] = "catchAllCellDelimiter" + ParsersConstantsCompiler["openChildren"] = "openChildren" + ParsersConstantsCompiler["joinChildrenWith"] = "joinChildrenWith" + ParsersConstantsCompiler["closeChildren"] = "closeChildren" +})(ParsersConstantsCompiler || (ParsersConstantsCompiler = {})) +var ParsersConstantsMisc +;(function (ParsersConstantsMisc) { + ParsersConstantsMisc["doNotSynthesize"] = "doNotSynthesize" +})(ParsersConstantsMisc || (ParsersConstantsMisc = {})) +var PreludeCellTypeIds +;(function (PreludeCellTypeIds) { + PreludeCellTypeIds["anyCell"] = "anyCell" + PreludeCellTypeIds["keywordCell"] = "keywordCell" + PreludeCellTypeIds["extraWordCell"] = "extraWordCell" + PreludeCellTypeIds["floatCell"] = "floatCell" + PreludeCellTypeIds["numberCell"] = "numberCell" + PreludeCellTypeIds["bitCell"] = "bitCell" + PreludeCellTypeIds["boolCell"] = "boolCell" + PreludeCellTypeIds["intCell"] = "intCell" +})(PreludeCellTypeIds || (PreludeCellTypeIds = {})) +var ParsersConstantsConstantTypes +;(function (ParsersConstantsConstantTypes) { + ParsersConstantsConstantTypes["boolean"] = "boolean" + ParsersConstantsConstantTypes["string"] = "string" + ParsersConstantsConstantTypes["int"] = "int" + ParsersConstantsConstantTypes["float"] = "float" +})(ParsersConstantsConstantTypes || (ParsersConstantsConstantTypes = {})) +var ParsersBundleFiles +;(function (ParsersBundleFiles) { + ParsersBundleFiles["package"] = "package.json" + ParsersBundleFiles["readme"] = "readme.md" + ParsersBundleFiles["indexHtml"] = "index.html" + ParsersBundleFiles["indexJs"] = "index.js" + ParsersBundleFiles["testJs"] = "test.js" +})(ParsersBundleFiles || (ParsersBundleFiles = {})) +var ParsersCellParser +;(function (ParsersCellParser) { + ParsersCellParser["prefix"] = "prefix" + ParsersCellParser["postfix"] = "postfix" + ParsersCellParser["omnifix"] = "omnifix" +})(ParsersCellParser || (ParsersCellParser = {})) +var ParsersConstants +;(function (ParsersConstants) { + // particle types + ParsersConstants["extensions"] = "extensions" + ParsersConstants["comment"] = "//" + ParsersConstants["parser"] = "parser" + ParsersConstants["cellType"] = "cellType" + ParsersConstants["parsersFileExtension"] = "parsers" + ParsersConstants["abstractParserPrefix"] = "abstract" + ParsersConstants["parserSuffix"] = "Parser" + ParsersConstants["cellTypeSuffix"] = "Cell" + // error check time + ParsersConstants["regex"] = "regex" + ParsersConstants["reservedWords"] = "reservedWords" + ParsersConstants["enumFromCellTypes"] = "enumFromCellTypes" + ParsersConstants["enum"] = "enum" + ParsersConstants["examples"] = "examples" + ParsersConstants["min"] = "min" + ParsersConstants["max"] = "max" + // baseParsers + ParsersConstants["baseParser"] = "baseParser" + ParsersConstants["blobParser"] = "blobParser" + ParsersConstants["errorParser"] = "errorParser" + // parse time + ParsersConstants["extends"] = "extends" + ParsersConstants["root"] = "root" + ParsersConstants["crux"] = "crux" + ParsersConstants["cruxFromId"] = "cruxFromId" + ParsersConstants["pattern"] = "pattern" + ParsersConstants["inScope"] = "inScope" + ParsersConstants["cells"] = "cells" + ParsersConstants["listDelimiter"] = "listDelimiter" + ParsersConstants["contentKey"] = "contentKey" + ParsersConstants["childrenKey"] = "childrenKey" + ParsersConstants["uniqueFirstWord"] = "uniqueFirstWord" + ParsersConstants["catchAllCellType"] = "catchAllCellType" + ParsersConstants["cellParser"] = "cellParser" + ParsersConstants["catchAllParser"] = "catchAllParser" + ParsersConstants["constants"] = "constants" + ParsersConstants["required"] = "required" + ParsersConstants["single"] = "single" + ParsersConstants["uniqueLine"] = "uniqueLine" + ParsersConstants["tags"] = "tags" + ParsersConstants["_rootNodeJsHeader"] = "_rootNodeJsHeader" + // default catchAll parser + ParsersConstants["BlobParser"] = "BlobParser" + ParsersConstants["DefaultRootParser"] = "DefaultRootParser" + // code + ParsersConstants["javascript"] = "javascript" + // compile time + ParsersConstants["compilerParser"] = "compiler" + ParsersConstants["compilesTo"] = "compilesTo" + // develop time + ParsersConstants["description"] = "description" + ParsersConstants["example"] = "example" + ParsersConstants["popularity"] = "popularity" + ParsersConstants["paint"] = "paint" +})(ParsersConstants || (ParsersConstants = {})) +class TypedWord extends ParticleWord { + constructor(particle, cellIndex, type) { + super(particle, cellIndex) + this._type = type + } + get type() { + return this._type + } + toString() { + return this.word + ":" + this.type + } +} +// todo: can we merge these methods into base Particle and ditch this class? +class ParserBackedParticle extends Particle { + get definition() { + if (this._definition) return this._definition + this._definition = this.isRoot() ? this.handParsersProgram : this.parent.definition.getParserDefinitionByParserId(this.constructor.name) + return this._definition + } + get rootParsersParticles() { + return this.definition.root + } + getAutocompleteResults(partialWord, cellIndex) { + return cellIndex === 0 ? this._getAutocompleteResultsForFirstWord(partialWord) : this._getAutocompleteResultsForCell(partialWord, cellIndex) + } + makeError(message) { + return new ParserDefinedError(this, message) + } + get particleIndex() { + // StringMap {firstWord: index} + // When there are multiple tails with the same firstWord, _index stores the last content. + // todo: change the above behavior: when a collision occurs, create an array. + return this._particleIndex || this._makeParticleIndex() + } + _clearIndex() { + delete this._particleIndex + return super._clearIndex() + } + _makeIndex(startAt = 0) { + if (this._particleIndex) this._makeParticleIndex(startAt) + return super._makeIndex(startAt) + } + _makeParticleIndex(startAt = 0) { + if (!this._particleIndex || !startAt) this._particleIndex = {} + const particles = this._getChildrenArray() + const newIndex = this._particleIndex + const length = particles.length + for (let index = startAt; index < length; index++) { + const particle = particles[index] + const ancestors = Array.from(particle.definition._getAncestorSet()).forEach(id => { + if (!newIndex[id]) newIndex[id] = [] + newIndex[id].push(particle) + }) + } + return newIndex + } + getChildInstancesOfParserId(parserId) { + return this.particleIndex[parserId] || [] + } + doesExtend(parserId) { + return this.definition._doesExtend(parserId) + } + _getErrorParserErrors() { + return [this.firstWord ? new UnknownParserError(this) : new BlankLineError(this)] + } + _getBlobParserCatchAllParser() { + return BlobParser + } + _getAutocompleteResultsForFirstWord(partialWord) { + const keywordMap = this.definition.firstWordMapWithDefinitions + let keywords = Object.keys(keywordMap) + if (partialWord) keywords = keywords.filter(keyword => keyword.includes(partialWord)) + return keywords + .map(keyword => { + const def = keywordMap[keyword] + if (def.suggestInAutocomplete === false) return false + const description = def.description + return { + text: keyword, + displayText: keyword + (description ? " " + description : "") + } + }) + .filter(i => i) + } + _getAutocompleteResultsForCell(partialWord, cellIndex) { + // todo: root should be [] correct? + const cell = this.parsedCells[cellIndex] + return cell ? cell.getAutoCompleteWords(partialWord) : [] + } + // note: this is overwritten by the root particle of a runtime parsers program. + // some of the magic that makes this all work. but maybe there's a better way. + get handParsersProgram() { + if (this.isRoot()) throw new Error(`Root particle without getHandParsersProgram defined.`) + return this.root.handParsersProgram + } + getRunTimeEnumOptions(cell) { + return undefined + } + getRunTimeEnumOptionsForValidation(cell) { + return this.getRunTimeEnumOptions(cell) + } + _sortParticlesByInScopeOrder() { + const parserOrder = this.definition._getMyInScopeParserIds() + if (!parserOrder.length) return this + const orderMap = {} + parserOrder.forEach((word, index) => (orderMap[word] = index)) + this.sort(Utils.makeSortByFn(runtimeParticle => orderMap[runtimeParticle.definition.parserIdFromDefinition])) + return this + } + get requiredParticleErrors() { + const errors = [] + Object.values(this.definition.firstWordMapWithDefinitions).forEach(def => { + if (def.isRequired() && !this.particleIndex[def.id]) errors.push(new MissingRequiredParserError(this, def.id)) + }) + return errors + } + get programAsCells() { + // todo: what is this? + return this.topDownArray.map(particle => { + const cells = particle.parsedCells + let indents = particle.getIndentLevel() - 1 + while (indents) { + cells.unshift(undefined) + indents-- + } + return cells + }) + } + get programWidth() { + return Math.max(...this.programAsCells.map(line => line.length)) + } + get allTypedWords() { + const words = [] + this.topDownArray.forEach(particle => particle.wordTypes.forEach((cell, index) => words.push(new TypedWord(particle, index, cell.cellTypeId)))) + return words + } + findAllWordsWithCellType(cellTypeId) { + return this.allTypedWords.filter(typedWord => typedWord.type === cellTypeId) + } + findAllParticlesWithParser(parserId) { + return this.topDownArray.filter(particle => particle.definition.parserIdFromDefinition === parserId) + } + toCellTypeParticles() { + return this.topDownArray.map(child => child.indentation + child.lineCellTypes).join("\n") + } + getParseTable(maxColumnWidth = 40) { + const particle = new Particle(this.toCellTypeParticles()) + return new Particle( + particle.topDownArray.map((particle, lineNumber) => { + const sourceParticle = this.particleAtLine(lineNumber) + const errs = sourceParticle.getErrors() + const errorCount = errs.length + const obj = { + lineNumber: lineNumber, + source: sourceParticle.indentation + sourceParticle.getLine(), + parser: sourceParticle.constructor.name, + cellTypes: particle.content, + errorCount: errorCount + } + if (errorCount) obj.errorMessages = errs.map(err => err.message).join(";") + return obj + }) + ).toFormattedTable(maxColumnWidth) + } + // Helper method for selecting potential parsers needed to update parsers file. + get invalidParsers() { + return Array.from( + new Set( + this.getAllErrors() + .filter(err => err instanceof UnknownParserError) + .map(err => err.getParticle().firstWord) + ) + ) + } + _getAllAutoCompleteWords() { + return this.getAllWordBoundaryCoordinates().map(coordinate => { + const results = this.getAutocompleteResultsAt(coordinate.lineIndex, coordinate.charIndex) + return { + lineIndex: coordinate.lineIndex, + charIndex: coordinate.charIndex, + wordIndex: coordinate.wordIndex, + word: results.word, + suggestions: results.matches + } + }) + } + toAutoCompleteCube(fillChar = "") { + const particles = [this.clone()] + const filled = this.clone().fill(fillChar) + this._getAllAutoCompleteWords().forEach(hole => { + hole.suggestions.forEach((suggestion, index) => { + if (!particles[index + 1]) particles[index + 1] = filled.clone() + particles[index + 1].particleAtLine(hole.lineIndex).setWord(hole.wordIndex, suggestion.text) + }) + }) + return new Particle(particles) + } + toAutoCompleteTable() { + return new Particle( + this._getAllAutoCompleteWords().map(result => { + result.suggestions = result.suggestions.map(particle => particle.text).join(" ") + return result + }) + ).asTable + } + getAutocompleteResultsAt(lineIndex, charIndex) { + const lineParticle = this.particleAtLine(lineIndex) || this + const particleInScope = lineParticle.getParticleInScopeAtCharIndex(charIndex) + // todo: add more tests + // todo: second param this.childrenToString() + // todo: change to getAutocomplete definitions + const wordIndex = lineParticle.getWordIndexAtCharacterIndex(charIndex) + const wordProperties = lineParticle.getWordProperties(wordIndex) + return { + startCharIndex: wordProperties.startCharIndex, + endCharIndex: wordProperties.endCharIndex, + word: wordProperties.word, + matches: particleInScope.getAutocompleteResults(wordProperties.word, wordIndex) + } + } + _sortWithParentParsersUpTop() { + const lineage = new HandParsersProgram(this.toString()).parserLineage + const rank = {} + lineage.topDownArray.forEach((particle, index) => { + rank[particle.getWord(0)] = index + }) + const particleAFirst = -1 + const particleBFirst = 1 + this.sort((particleA, particleB) => { + const particleARank = rank[particleA.getWord(0)] + const particleBRank = rank[particleB.getWord(0)] + return particleARank < particleBRank ? particleAFirst : particleBFirst + }) + return this + } + format() { + if (this.isRoot()) { + this._sortParticlesByInScopeOrder() + try { + this._sortWithParentParsersUpTop() + } catch (err) { + console.log(`Warning: ${err}`) + } + } + this.topDownArray.forEach(child => child.format()) + return this + } + getParserUsage(filepath = "") { + // returns a report on what parsers from its language the program uses + const usage = new Particle() + const handParsersProgram = this.handParsersProgram + handParsersProgram.validConcreteAndAbstractParserDefinitions.forEach(def => { + const requiredCellTypeIds = def.cellParser.getRequiredCellTypeIds() + usage.appendLine([def.parserIdFromDefinition, "line-id", "parser", requiredCellTypeIds.join(" ")].join(" ")) + }) + this.topDownArray.forEach((particle, lineNumber) => { + const stats = usage.getParticle(particle.parserId) + stats.appendLine([filepath + "-" + lineNumber, particle.words.join(" ")].join(" ")) + }) + return usage + } + toPaintParticles() { + return this.topDownArray.map(child => child.indentation + child.getLinePaints()).join("\n") + } + toDefinitionLineNumberParticles() { + return this.topDownArray.map(child => child.definition.lineNumber + " " + child.indentation + child.cellDefinitionLineNumbers.join(" ")).join("\n") + } + get asCellTypeParticlesWithParserIds() { + return this.topDownArray.map(child => child.constructor.name + this.wordBreakSymbol + child.indentation + child.lineCellTypes).join("\n") + } + toPreludeCellTypeParticlesWithParserIds() { + return this.topDownArray.map(child => child.constructor.name + this.wordBreakSymbol + child.indentation + child.getLineCellPreludeTypes()).join("\n") + } + get asParticlesWithParsers() { + return this.topDownArray.map(child => child.constructor.name + this.wordBreakSymbol + child.indentation + child.getLine()).join("\n") + } + getCellPaintAtPosition(lineIndex, wordIndex) { + this._initCellTypeCache() + const typeParticle = this._cache_paintParticles.topDownArray[lineIndex - 1] + return typeParticle ? typeParticle.getWord(wordIndex - 1) : undefined + } + _initCellTypeCache() { + const particleMTime = this.getLineOrChildrenModifiedTime() + if (this._cache_programCellTypeStringMTime === particleMTime) return undefined + this._cache_typeParticles = new Particle(this.toCellTypeParticles()) + this._cache_paintParticles = new Particle(this.toPaintParticles()) + this._cache_programCellTypeStringMTime = particleMTime + } + createParserCombinator() { + return this.isRoot() ? new Particle.ParserCombinator(BlobParser) : new Particle.ParserCombinator(this.parent._getParser()._getCatchAllParser(this.parent), {}) + } + get parserId() { + return this.definition.parserIdFromDefinition + } + get wordTypes() { + return this.parsedCells.filter(cell => cell.getWord() !== undefined) + } + get cellErrors() { + const { parsedCells } = this // todo: speedup. takes ~3s on pldb. + // todo: speedup getErrorIfAny. takes ~3s on pldb. + return parsedCells.map(check => check.getErrorIfAny()).filter(identity => identity) + } + get singleParserUsedTwiceErrors() { + const errors = [] + const parent = this.parent + const hits = parent.getChildInstancesOfParserId(this.definition.id) + if (hits.length > 1) + hits.forEach((particle, index) => { + if (particle === this) errors.push(new ParserUsedMultipleTimesError(particle)) + }) + return errors + } + get uniqueLineAppearsTwiceErrors() { + const errors = [] + const parent = this.parent + const hits = parent.getChildInstancesOfParserId(this.definition.id) + if (hits.length > 1) { + const set = new Set() + hits.forEach((particle, index) => { + const line = particle.getLine() + if (set.has(line)) errors.push(new ParserUsedMultipleTimesError(particle)) + set.add(line) + }) + } + return errors + } + get scopeErrors() { + let errors = [] + const def = this.definition + if (def.isSingle) errors = errors.concat(this.singleParserUsedTwiceErrors) // todo: speedup. takes ~1s on pldb. + if (def.isUniqueLine) errors = errors.concat(this.uniqueLineAppearsTwiceErrors) // todo: speedup. takes ~1s on pldb. + const { requiredParticleErrors } = this // todo: speedup. takes ~1.5s on pldb. + if (requiredParticleErrors.length) errors = errors.concat(requiredParticleErrors) + return errors + } + getErrors() { + return this.cellErrors.concat(this.scopeErrors) + } + get parsedCells() { + return this.definition.cellParser.getCellArray(this) + } + // todo: just make a fn that computes proper spacing and then is given a particle to print + get lineCellTypes() { + return this.parsedCells.map(slot => slot.cellTypeId).join(" ") + } + getLineCellPreludeTypes() { + return this.parsedCells + .map(slot => { + const def = slot.cellTypeDefinition + //todo: cleanup + return def ? def.preludeKindId : PreludeCellTypeIds.anyCell + }) + .join(" ") + } + getLinePaints(defaultScope = "source") { + return this.parsedCells.map(slot => slot.paint || defaultScope).join(" ") + } + get cellDefinitionLineNumbers() { + return this.parsedCells.map(cell => cell.definitionLineNumber) + } + _getCompiledIndentation() { + const indentCharacter = this.definition._getCompilerObject()[ParsersConstantsCompiler.indentCharacter] + const indent = this.indentation + return indentCharacter !== undefined ? indentCharacter.repeat(indent.length) : indent + } + _getFields() { + // fields are like cells + const fields = {} + this.forEach(particle => { + const def = particle.definition + if (def.isRequired() || def.isSingle) fields[particle.getWord(0)] = particle.content + }) + return fields + } + _getCompiledLine() { + const compiler = this.definition._getCompilerObject() + const catchAllCellDelimiter = compiler[ParsersConstantsCompiler.catchAllCellDelimiter] + const str = compiler[ParsersConstantsCompiler.stringTemplate] + return str !== undefined ? Utils.formatStr(str, catchAllCellDelimiter, Object.assign(this._getFields(), this.cells)) : this.getLine() + } + get listDelimiter() { + return this.definition._getFromExtended(ParsersConstants.listDelimiter) + } + get contentKey() { + return this.definition._getFromExtended(ParsersConstants.contentKey) + } + get childrenKey() { + return this.definition._getFromExtended(ParsersConstants.childrenKey) + } + get childrenAreTextBlob() { + return this.definition._isBlobParser() + } + get isArrayElement() { + return this.definition._hasFromExtended(ParsersConstants.uniqueFirstWord) ? false : !this.definition.isSingle + } + get list() { + return this.listDelimiter ? this.content.split(this.listDelimiter) : super.list + } + get typedContent() { + // todo: probably a better way to do this, perhaps by defining a cellDelimiter at the particle level + // todo: this currently parse anything other than string types + if (this.listDelimiter) return this.content.split(this.listDelimiter) + const cells = this.parsedCells + if (cells.length === 2) return cells[1].parsed + return this.content + } + get typedTuple() { + const key = this.firstWord + if (this.childrenAreTextBlob) return [key, this.childrenToString()] + const { typedContent, contentKey, childrenKey } = this + if (contentKey || childrenKey) { + let obj = {} + if (childrenKey) obj[childrenKey] = this.childrenToString() + else obj = this.typedMap + if (contentKey) { + obj[contentKey] = typedContent + } + return [key, obj] + } + const hasChildren = this.length > 0 + const hasChildrenNoContent = typedContent === undefined && hasChildren + const shouldReturnValueAsObject = hasChildrenNoContent + if (shouldReturnValueAsObject) return [key, this.typedMap] + const hasChildrenAndContent = typedContent !== undefined && hasChildren + const shouldReturnValueAsContentPlusChildren = hasChildrenAndContent + // If the particle has a content and a subparticle return it as a string, as + // Javascript object values can't be both a leaf and a particle. + if (shouldReturnValueAsContentPlusChildren) return [key, this.contentWithChildren] + return [key, typedContent] + } + get _shouldSerialize() { + const should = this.shouldSerialize + return should === undefined ? true : should + } + get typedMap() { + const obj = {} + this.forEach(particle => { + if (!particle._shouldSerialize) return true + const tuple = particle.typedTuple + if (!particle.isArrayElement) obj[tuple[0]] = tuple[1] + else { + if (!obj[tuple[0]]) obj[tuple[0]] = [] + obj[tuple[0]].push(tuple[1]) + } + }) + return obj + } + fromTypedMap() {} + compile() { + if (this.isRoot()) return super.compile() + const def = this.definition + const indent = this._getCompiledIndentation() + const compiledLine = this._getCompiledLine() + if (def.isTerminalParser()) return indent + compiledLine + const compiler = def._getCompilerObject() + const openChildrenString = compiler[ParsersConstantsCompiler.openChildren] || "" + const closeChildrenString = compiler[ParsersConstantsCompiler.closeChildren] || "" + const childJoinCharacter = compiler[ParsersConstantsCompiler.joinChildrenWith] || "\n" + const compiledChildren = this.map(child => child.compile()).join(childJoinCharacter) + return `${indent + compiledLine}${openChildrenString} +${compiledChildren} +${indent}${closeChildrenString}` + } + // todo: remove + get cells() { + const cells = {} + this.parsedCells.forEach(cell => { + const cellTypeId = cell.cellTypeId + if (!cell.isCatchAll()) cells[cellTypeId] = cell.parsed + else { + if (!cells[cellTypeId]) cells[cellTypeId] = [] + cells[cellTypeId].push(cell.parsed) + } + }) + return cells + } +} +class BlobParser extends ParserBackedParticle { + createParserCombinator() { + return new Particle.ParserCombinator(BlobParser, {}) + } + getErrors() { + return [] + } +} +// todo: can we remove this? hard to extend. +class UnknownParserParticle extends ParserBackedParticle { + createParserCombinator() { + return new Particle.ParserCombinator(UnknownParserParticle, {}) + } + getErrors() { + return [new UnknownParserError(this)] + } +} +/* +A cell contains a word but also the type information for that word. +*/ +class AbstractParsersBackedCell { + constructor(particle, index, typeDef, cellTypeId, isCatchAll, parserDefinitionParser) { + this._typeDef = typeDef + this._particle = particle + this._isCatchAll = isCatchAll + this._index = index + this._cellTypeId = cellTypeId + this._parserDefinitionParser = parserDefinitionParser + } + getWord() { + return this._particle.getWord(this._index) + } + get definitionLineNumber() { + return this._typeDef.lineNumber + } + get cellTypeId() { + return this._cellTypeId + } + getParticle() { + return this._particle + } + get cellIndex() { + return this._index + } + isCatchAll() { + return this._isCatchAll + } + get min() { + return this.cellTypeDefinition.get(ParsersConstants.min) || "0" + } + get max() { + return this.cellTypeDefinition.get(ParsersConstants.max) || "100" + } + get placeholder() { + return this.cellTypeDefinition.get(ParsersConstants.examples) || "" + } + get paint() { + const definition = this.cellTypeDefinition + if (definition) return definition.paint // todo: why the undefined? + } + getAutoCompleteWords(partialWord = "") { + const cellDef = this.cellTypeDefinition + let words = cellDef ? cellDef._getAutocompleteWordOptions(this.getParticle().root) : [] + const runTimeOptions = this.getParticle().getRunTimeEnumOptions(this) + if (runTimeOptions) words = runTimeOptions.concat(words) + if (partialWord) words = words.filter(word => word.includes(partialWord)) + return words.map(word => { + return { + text: word, + displayText: word + } + }) + } + synthesizeCell(seed = Date.now()) { + // todo: cleanup + const cellDef = this.cellTypeDefinition + const enumOptions = cellDef._getFromExtended(ParsersConstants.enum) + if (enumOptions) return Utils.getRandomString(1, enumOptions.split(" ")) + return this._synthesizeCell(seed) + } + _getStumpEnumInput(crux) { + const cellDef = this.cellTypeDefinition + const enumOptions = cellDef._getFromExtended(ParsersConstants.enum) + if (!enumOptions) return undefined + const options = new Particle( + enumOptions + .split(" ") + .map(option => `option ${option}`) + .join("\n") + ) + return `select + name ${crux} +${options.toString(1)}` + } + _toStumpInput(crux) { + // todo: remove + const enumInput = this._getStumpEnumInput(crux) + if (enumInput) return enumInput + // todo: cleanup. We shouldn't have these dual cellType classes. + return `input + name ${crux} + placeholder ${this.placeholder}` + } + get cellTypeDefinition() { + return this._typeDef + } + _getErrorContext() { + return this.getParticle().getLine().split(" ")[0] // todo: WordBreakSymbol + } + isValid() { + const runTimeOptions = this.getParticle().getRunTimeEnumOptionsForValidation(this) + const word = this.getWord() + if (runTimeOptions) return runTimeOptions.includes(word) + return this.cellTypeDefinition.isValid(word, this.getParticle().root) && this._isValid() + } + getErrorIfAny() { + const word = this.getWord() + if (word !== undefined && this.isValid()) return undefined + // todo: refactor invalidwordError. We want better error messages. + return word === undefined || word === "" ? new MissingWordError(this) : new InvalidWordError(this) + } +} +AbstractParsersBackedCell.parserFunctionName = "" +class ParsersBitCell extends AbstractParsersBackedCell { + _isValid() { + const word = this.getWord() + return word === "0" || word === "1" + } + _synthesizeCell() { + return Utils.getRandomString(1, "01".split("")) + } + get regexString() { + return "[01]" + } + get parsed() { + const word = this.getWord() + return !!parseInt(word) + } +} +ParsersBitCell.defaultPaint = "constant.numeric" +class ParsersNumericCell extends AbstractParsersBackedCell { + _toStumpInput(crux) { + return `input + name ${crux} + type number + placeholder ${this.placeholder} + min ${this.min} + max ${this.max}` + } +} +class ParsersIntCell extends ParsersNumericCell { + _isValid() { + const word = this.getWord() + const num = parseInt(word) + if (isNaN(num)) return false + return num.toString() === word + } + _synthesizeCell(seed) { + return Utils.randomUniformInt(parseInt(this.min), parseInt(this.max), seed).toString() + } + get regexString() { + return "-?[0-9]+" + } + get parsed() { + const word = this.getWord() + return parseInt(word) + } +} +ParsersIntCell.defaultPaint = "constant.numeric.integer" +ParsersIntCell.parserFunctionName = "parseInt" +class ParsersFloatCell extends ParsersNumericCell { + _isValid() { + const word = this.getWord() + const num = parseFloat(word) + return !isNaN(num) && /^-?\d*(\.\d+)?([eE][+-]?\d+)?$/.test(word) + } + _synthesizeCell(seed) { + return Utils.randomUniformFloat(parseFloat(this.min), parseFloat(this.max), seed).toString() + } + get regexString() { + return "-?d*(.d+)?" + } + get parsed() { + const word = this.getWord() + return parseFloat(word) + } +} +ParsersFloatCell.defaultPaint = "constant.numeric.float" +ParsersFloatCell.parserFunctionName = "parseFloat" +// ErrorCellType => parsers asks for a '' cell type here but the parsers does not specify a '' cell type. (todo: bring in didyoumean?) +class ParsersBoolCell extends AbstractParsersBackedCell { + constructor() { + super(...arguments) + this._trues = new Set(["1", "true", "t", "yes"]) + this._falses = new Set(["0", "false", "f", "no"]) + } + _isValid() { + const word = this.getWord() + const str = word.toLowerCase() + return this._trues.has(str) || this._falses.has(str) + } + _synthesizeCell() { + return Utils.getRandomString(1, ["1", "true", "t", "yes", "0", "false", "f", "no"]) + } + _getOptions() { + return Array.from(this._trues).concat(Array.from(this._falses)) + } + get regexString() { + return "(?:" + this._getOptions().join("|") + ")" + } + get parsed() { + const word = this.getWord() + return this._trues.has(word.toLowerCase()) + } +} +ParsersBoolCell.defaultPaint = "constant.numeric" +class ParsersAnyCell extends AbstractParsersBackedCell { + _isValid() { + return true + } + _synthesizeCell() { + const examples = this.cellTypeDefinition._getFromExtended(ParsersConstants.examples) + if (examples) return Utils.getRandomString(1, examples.split(" ")) + return this._parserDefinitionParser.parserIdFromDefinition + "-" + this.constructor.name + } + get regexString() { + return "[^ ]+" + } + get parsed() { + return this.getWord() + } +} +class ParsersKeywordCell extends ParsersAnyCell { + _synthesizeCell() { + return this._parserDefinitionParser.cruxIfAny + } +} +ParsersKeywordCell.defaultPaint = "keyword" +class ParsersExtraWordCellTypeCell extends AbstractParsersBackedCell { + _isValid() { + return false + } + synthesizeCell() { + throw new Error(`Trying to synthesize a ParsersExtraWordCellTypeCell`) + return this._synthesizeCell() + } + _synthesizeCell() { + return "extraWord" // should never occur? + } + get parsed() { + return this.getWord() + } + getErrorIfAny() { + return new ExtraWordError(this) + } +} +class ParsersUnknownCellTypeCell extends AbstractParsersBackedCell { + _isValid() { + return false + } + synthesizeCell() { + throw new Error(`Trying to synthesize an ParsersUnknownCellTypeCell`) + return this._synthesizeCell() + } + _synthesizeCell() { + return "extraWord" // should never occur? + } + get parsed() { + return this.getWord() + } + getErrorIfAny() { + return new UnknownCellTypeError(this) + } +} +class AbstractParticleError { + constructor(particle) { + this._particle = particle + } + getLineIndex() { + return this.lineNumber - 1 + } + get lineNumber() { + return this.getParticle()._getLineNumber() // todo: handle sourcemaps + } + isCursorOnWord(lineIndex, characterIndex) { + return lineIndex === this.getLineIndex() && this._doesCharacterIndexFallOnWord(characterIndex) + } + _doesCharacterIndexFallOnWord(characterIndex) { + return this.cellIndex === this.getParticle().getWordIndexAtCharacterIndex(characterIndex) + } + // convenience method. may be removed. + isBlankLineError() { + return false + } + // convenience method. may be removed. + isMissingWordError() { + return false + } + getIndent() { + return this.getParticle().indentation + } + getCodeMirrorLineWidgetElement(onApplySuggestionCallBack = () => {}) { + const suggestion = this.suggestionMessage + if (this.isMissingWordError()) return this._getCodeMirrorLineWidgetElementCellTypeHints() + if (suggestion) return this._getCodeMirrorLineWidgetElementWithSuggestion(onApplySuggestionCallBack, suggestion) + return this._getCodeMirrorLineWidgetElementWithoutSuggestion() + } + get parserId() { + return this.getParticle().definition.parserIdFromDefinition + } + _getCodeMirrorLineWidgetElementCellTypeHints() { + const el = document.createElement("div") + el.appendChild(document.createTextNode(this.getIndent() + this.getParticle().definition.lineHints)) + el.className = "LintCellTypeHints" + return el + } + _getCodeMirrorLineWidgetElementWithoutSuggestion() { + const el = document.createElement("div") + el.appendChild(document.createTextNode(this.getIndent() + this.message)) + el.className = "LintError" + return el + } + _getCodeMirrorLineWidgetElementWithSuggestion(onApplySuggestionCallBack, suggestion) { + const el = document.createElement("div") + el.appendChild(document.createTextNode(this.getIndent() + `${this.errorTypeName}. Suggestion: ${suggestion}`)) + el.className = "LintErrorWithSuggestion" + el.onclick = () => { + this.applySuggestion() + onApplySuggestionCallBack() + } + return el + } + getLine() { + return this.getParticle().getLine() + } + getExtension() { + return this.getParticle().handParsersProgram.extensionName + } + getParticle() { + return this._particle + } + get errorTypeName() { + return this.constructor.name.replace("Error", "") + } + get cellIndex() { + return 0 + } + toObject() { + return { + type: this.errorTypeName, + line: this.lineNumber, + cell: this.cellIndex, + suggestion: this.suggestionMessage, + path: this.getParticle().getFirstWordPath(), + message: this.message + } + } + hasSuggestion() { + return this.suggestionMessage !== "" + } + get suggestionMessage() { + return "" + } + toString() { + return this.message + } + applySuggestion() {} + get message() { + return `${this.errorTypeName} at line ${this.lineNumber} cell ${this.cellIndex}.` + } +} +class AbstractCellError extends AbstractParticleError { + constructor(cell) { + super(cell.getParticle()) + this._cell = cell + } + get cell() { + return this._cell + } + get cellIndex() { + return this._cell.cellIndex + } + get wordSuggestion() { + return Utils.didYouMean( + this.cell.getWord(), + this.cell.getAutoCompleteWords().map(option => option.text) + ) + } +} +class UnknownParserError extends AbstractParticleError { + get message() { + const particle = this.getParticle() + const parentParticle = particle.parent + const options = parentParticle._getParser().getFirstWordOptions() + return super.message + ` Invalid parser "${particle.firstWord}". Valid parsers are: ${Utils._listToEnglishText(options, 7)}.` + } + get wordSuggestion() { + const particle = this.getParticle() + const parentParticle = particle.parent + return Utils.didYouMean( + particle.firstWord, + parentParticle.getAutocompleteResults("", 0).map(option => option.text) + ) + } + get suggestionMessage() { + const suggestion = this.wordSuggestion + const particle = this.getParticle() + if (suggestion) return `Change "${particle.firstWord}" to "${suggestion}"` + return "" + } + applySuggestion() { + const suggestion = this.wordSuggestion + if (suggestion) this.getParticle().setWord(this.cellIndex, suggestion) + return this + } +} +class ParserDefinedError extends AbstractParticleError { + constructor(particle, message) { + super() + this._particle = particle + this._message = message + } + get message() { + return this._message + } +} +class BlankLineError extends UnknownParserError { + get message() { + return super.message + ` Line: "${this.getParticle().getLine()}". Blank lines are errors.` + } + // convenience method + isBlankLineError() { + return true + } + get suggestionMessage() { + return `Delete line ${this.lineNumber}` + } + applySuggestion() { + this.getParticle().destroy() + return this + } +} +class MissingRequiredParserError extends AbstractParticleError { + constructor(particle, missingParserId) { + super(particle) + this._missingParserId = missingParserId + } + get message() { + return super.message + ` A "${this._missingParserId}" is required.` + } +} +class ParserUsedMultipleTimesError extends AbstractParticleError { + get message() { + return super.message + ` Multiple "${this.getParticle().firstWord}" found.` + } + get suggestionMessage() { + return `Delete line ${this.lineNumber}` + } + applySuggestion() { + return this.getParticle().destroy() + } +} +class LineAppearsMultipleTimesError extends AbstractParticleError { + get message() { + return super.message + ` "${this.getParticle().getLine()}" appears multiple times.` + } + get suggestionMessage() { + return `Delete line ${this.lineNumber}` + } + applySuggestion() { + return this.getParticle().destroy() + } +} +class UnknownCellTypeError extends AbstractCellError { + get message() { + return super.message + ` No cellType "${this.cell.cellTypeId}" found. Language parsers for "${this.getExtension()}" may need to be fixed.` + } +} +class InvalidWordError extends AbstractCellError { + get message() { + return super.message + ` "${this.cell.getWord()}" does not fit in cellType "${this.cell.cellTypeId}".` + } + get suggestionMessage() { + const suggestion = this.wordSuggestion + if (suggestion) return `Change "${this.cell.getWord()}" to "${suggestion}"` + return "" + } + applySuggestion() { + const suggestion = this.wordSuggestion + if (suggestion) this.getParticle().setWord(this.cellIndex, suggestion) + return this + } +} +class ExtraWordError extends AbstractCellError { + get message() { + return super.message + ` Extra word "${this.cell.getWord()}" in ${this.parserId}.` + } + get suggestionMessage() { + return `Delete word "${this.cell.getWord()}" at cell ${this.cellIndex}` + } + applySuggestion() { + return this.getParticle().deleteWordAt(this.cellIndex) + } +} +class MissingWordError extends AbstractCellError { + // todo: autocomplete suggestion + get message() { + return super.message + ` Missing word for cell "${this.cell.cellTypeId}".` + } + isMissingWordError() { + return true + } +} +// todo: add standard types, enum types, from disk types +class AbstractParsersWordTestParser extends Particle {} +class ParsersRegexTestParser extends AbstractParsersWordTestParser { + isValid(str) { + if (!this._regex) this._regex = new RegExp("^" + this.content + "$") + return !!str.match(this._regex) + } +} +class ParsersReservedWordsTestParser extends AbstractParsersWordTestParser { + isValid(str) { + if (!this._set) this._set = new Set(this.content.split(" ")) + return !this._set.has(str) + } +} +// todo: remove in favor of custom word type constructors +class EnumFromCellTypesTestParser extends AbstractParsersWordTestParser { + _getEnumFromCellTypes(programRootParticle) { + const cellTypeIds = this.getWordsFrom(1) + const enumGroup = cellTypeIds.join(" ") + // note: hack where we store it on the program. otherwise has global effects. + if (!programRootParticle._enumMaps) programRootParticle._enumMaps = {} + if (programRootParticle._enumMaps[enumGroup]) return programRootParticle._enumMaps[enumGroup] + const wordIndex = 1 + const map = {} + const cellTypeMap = {} + cellTypeIds.forEach(typeId => (cellTypeMap[typeId] = true)) + programRootParticle.allTypedWords + .filter(typedWord => cellTypeMap[typedWord.type]) + .forEach(typedWord => { + map[typedWord.word] = true + }) + programRootParticle._enumMaps[enumGroup] = map + return map + } + // todo: remove + isValid(str, programRootParticle) { + return this._getEnumFromCellTypes(programRootParticle)[str] === true + } +} +class ParsersEnumTestParticle extends AbstractParsersWordTestParser { + isValid(str) { + // enum c c++ java + return !!this.getOptions()[str] + } + getOptions() { + if (!this._map) this._map = Utils.arrayToMap(this.getWordsFrom(1)) + return this._map + } +} +class cellTypeDefinitionParser extends AbstractExtendibleParticle { + createParserCombinator() { + const types = {} + types[ParsersConstants.regex] = ParsersRegexTestParser + types[ParsersConstants.reservedWords] = ParsersReservedWordsTestParser + types[ParsersConstants.enumFromCellTypes] = EnumFromCellTypesTestParser + types[ParsersConstants.enum] = ParsersEnumTestParticle + types[ParsersConstants.paint] = Particle + types[ParsersConstants.comment] = Particle + types[ParsersConstants.examples] = Particle + types[ParsersConstants.min] = Particle + types[ParsersConstants.max] = Particle + types[ParsersConstants.description] = Particle + types[ParsersConstants.extends] = Particle + return new Particle.ParserCombinator(undefined, types) + } + get id() { + return this.getWord(0) + } + get idToParticleMap() { + return this.parent.cellTypeDefinitions + } + getGetter(wordIndex) { + const wordToNativeJavascriptTypeParser = this.getCellConstructor().parserFunctionName + return `get ${this.cellTypeId}() { + return ${wordToNativeJavascriptTypeParser ? wordToNativeJavascriptTypeParser + `(this.getWord(${wordIndex}))` : `this.getWord(${wordIndex})`} + }` + } + getCatchAllGetter(wordIndex) { + const wordToNativeJavascriptTypeParser = this.getCellConstructor().parserFunctionName + return `get ${this.cellTypeId}() { + return ${wordToNativeJavascriptTypeParser ? `this.getWordsFrom(${wordIndex}).map(val => ${wordToNativeJavascriptTypeParser}(val))` : `this.getWordsFrom(${wordIndex})`} + }` + } + // `this.getWordsFrom(${requireds.length + 1})` + // todo: cleanup typings. todo: remove this hidden logic. have a "baseType" property? + getCellConstructor() { + return this.preludeKind || ParsersAnyCell + } + get preludeKind() { + return PreludeKinds[this.getWord(0)] || PreludeKinds[this._getExtendedCellTypeId()] + } + get preludeKindId() { + if (PreludeKinds[this.getWord(0)]) return this.getWord(0) + else if (PreludeKinds[this._getExtendedCellTypeId()]) return this._getExtendedCellTypeId() + return PreludeCellTypeIds.anyCell + } + _getExtendedCellTypeId() { + const arr = this._getAncestorsArray() + return arr[arr.length - 1].id + } + get paint() { + const hs = this._getFromExtended(ParsersConstants.paint) + if (hs) return hs + const preludeKind = this.preludeKind + if (preludeKind) return preludeKind.defaultPaint + } + _getEnumOptions() { + const enumParticle = this._getParticleFromExtended(ParsersConstants.enum) + if (!enumParticle) return undefined + // we sort by longest first to capture longest match first. todo: add test + const options = Object.keys(enumParticle.getParticle(ParsersConstants.enum).getOptions()) + options.sort((a, b) => b.length - a.length) + return options + } + _getEnumFromCellTypeOptions(program) { + const particle = this._getParticleFromExtended(ParsersConstants.enumFromCellTypes) + return particle ? Object.keys(particle.getParticle(ParsersConstants.enumFromCellTypes)._getEnumFromCellTypes(program)) : undefined + } + _getAutocompleteWordOptions(program) { + return this._getEnumOptions() || this._getEnumFromCellTypeOptions(program) || [] + } + get regexString() { + // todo: enum + const enumOptions = this._getEnumOptions() + return this._getFromExtended(ParsersConstants.regex) || (enumOptions ? "(?:" + enumOptions.join("|") + ")" : "[^ ]*") + } + _getAllTests() { + return this._getChildrenByParserInExtended(AbstractParsersWordTestParser) + } + isValid(str, programRootParticle) { + return this._getAllTests().every(particle => particle.isValid(str, programRootParticle)) + } + get cellTypeId() { + return this.getWord(0) + } +} +class AbstractCellParser { + constructor(definition) { + this._definition = definition + } + get catchAllCellTypeId() { + return this._definition._getFromExtended(ParsersConstants.catchAllCellType) + } + // todo: improve layout (use bold?) + get lineHints() { + const catchAllCellTypeId = this.catchAllCellTypeId + const parserId = this._definition.cruxIfAny || this._definition.id // todo: cleanup + return `${parserId}: ${this.getRequiredCellTypeIds().join(" ")}${catchAllCellTypeId ? ` ${catchAllCellTypeId}...` : ""}` + } + getRequiredCellTypeIds() { + if (!this._requiredCellTypeIds) { + const parameters = this._definition._getFromExtended(ParsersConstants.cells) + this._requiredCellTypeIds = parameters ? parameters.split(" ") : [] + } + return this._requiredCellTypeIds + } + _getCellTypeId(cellIndex, requiredCellTypeIds, totalWordCount) { + return requiredCellTypeIds[cellIndex] + } + _isCatchAllCell(cellIndex, numberOfRequiredCells, totalWordCount) { + return cellIndex >= numberOfRequiredCells + } + getCellArray(particle = undefined) { + const wordCount = particle ? particle.words.length : 0 + const def = this._definition + const parsersProgram = def.languageDefinitionProgram + const requiredCellTypeIds = this.getRequiredCellTypeIds() + const numberOfRequiredCells = requiredCellTypeIds.length + const actualWordCountOrRequiredCellCount = Math.max(wordCount, numberOfRequiredCells) + const cells = [] + // A for loop instead of map because "numberOfCellsToFill" can be longer than words.length + for (let cellIndex = 0; cellIndex < actualWordCountOrRequiredCellCount; cellIndex++) { + const isCatchAll = this._isCatchAllCell(cellIndex, numberOfRequiredCells, wordCount) + let cellTypeId = isCatchAll ? this.catchAllCellTypeId : this._getCellTypeId(cellIndex, requiredCellTypeIds, wordCount) + let cellTypeDefinition = parsersProgram.getCellTypeDefinitionById(cellTypeId) + let cellConstructor + if (cellTypeDefinition) cellConstructor = cellTypeDefinition.getCellConstructor() + else if (cellTypeId) cellConstructor = ParsersUnknownCellTypeCell + else { + cellConstructor = ParsersExtraWordCellTypeCell + cellTypeId = PreludeCellTypeIds.extraWordCell + cellTypeDefinition = parsersProgram.getCellTypeDefinitionById(cellTypeId) + } + const anyCellConstructor = cellConstructor + cells[cellIndex] = new anyCellConstructor(particle, cellIndex, cellTypeDefinition, cellTypeId, isCatchAll, def) + } + return cells + } +} +class PrefixCellParser extends AbstractCellParser {} +class PostfixCellParser extends AbstractCellParser { + _isCatchAllCell(cellIndex, numberOfRequiredCells, totalWordCount) { + return cellIndex < totalWordCount - numberOfRequiredCells + } + _getCellTypeId(cellIndex, requiredCellTypeIds, totalWordCount) { + const catchAllWordCount = Math.max(totalWordCount - requiredCellTypeIds.length, 0) + return requiredCellTypeIds[cellIndex - catchAllWordCount] + } +} +class OmnifixCellParser extends AbstractCellParser { + getCellArray(particle = undefined) { + const cells = [] + const def = this._definition + const program = particle ? particle.root : undefined + const parsersProgram = def.languageDefinitionProgram + const words = particle ? particle.words : [] + const requiredCellTypeDefs = this.getRequiredCellTypeIds().map(cellTypeId => parsersProgram.getCellTypeDefinitionById(cellTypeId)) + const catchAllCellTypeId = this.catchAllCellTypeId + const catchAllCellTypeDef = catchAllCellTypeId && parsersProgram.getCellTypeDefinitionById(catchAllCellTypeId) + words.forEach((word, wordIndex) => { + let cellConstructor + for (let index = 0; index < requiredCellTypeDefs.length; index++) { + const cellTypeDefinition = requiredCellTypeDefs[index] + if (cellTypeDefinition.isValid(word, program)) { + // todo: cleanup cellIndex/wordIndex stuff + cellConstructor = cellTypeDefinition.getCellConstructor() + cells.push(new cellConstructor(particle, wordIndex, cellTypeDefinition, cellTypeDefinition.id, false, def)) + requiredCellTypeDefs.splice(index, 1) + return true + } + } + if (catchAllCellTypeDef && catchAllCellTypeDef.isValid(word, program)) { + cellConstructor = catchAllCellTypeDef.getCellConstructor() + cells.push(new cellConstructor(particle, wordIndex, catchAllCellTypeDef, catchAllCellTypeId, true, def)) + return true + } + cells.push(new ParsersUnknownCellTypeCell(particle, wordIndex, undefined, undefined, false, def)) + }) + const wordCount = words.length + requiredCellTypeDefs.forEach((cellTypeDef, index) => { + let cellConstructor = cellTypeDef.getCellConstructor() + cells.push(new cellConstructor(particle, wordCount + index, cellTypeDef, cellTypeDef.id, false, def)) + }) + return cells + } +} +class ParsersExampleParser extends Particle {} +class ParsersCompilerParser extends Particle { + createParserCombinator() { + const types = [ + ParsersConstantsCompiler.stringTemplate, + ParsersConstantsCompiler.indentCharacter, + ParsersConstantsCompiler.catchAllCellDelimiter, + ParsersConstantsCompiler.joinChildrenWith, + ParsersConstantsCompiler.openChildren, + ParsersConstantsCompiler.closeChildren + ] + const map = {} + types.forEach(type => { + map[type] = Particle + }) + return new Particle.ParserCombinator(undefined, map) + } +} +class AbstractParserConstantParser extends Particle { + constructor(children, line, parent) { + super(children, line, parent) + parent[this.identifier] = this.constantValue + } + getGetter() { + return `get ${this.identifier}() { return ${this.constantValueAsJsText} }` + } + get identifier() { + return this.getWord(1) + } + get constantValueAsJsText() { + const words = this.getWordsFrom(2) + return words.length > 1 ? `[${words.join(",")}]` : words[0] + } + get constantValue() { + return JSON.parse(this.constantValueAsJsText) + } +} +class ParsersParserConstantInt extends AbstractParserConstantParser {} +class ParsersParserConstantString extends AbstractParserConstantParser { + get constantValueAsJsText() { + return "`" + Utils.escapeBackTicks(this.constantValue) + "`" + } + get constantValue() { + return this.length ? this.childrenToString() : this.getWordsFrom(2).join(" ") + } +} +class ParsersParserConstantFloat extends AbstractParserConstantParser {} +class ParsersParserConstantBoolean extends AbstractParserConstantParser {} +class AbstractParserDefinitionParser extends AbstractExtendibleParticle { + createParserCombinator() { + // todo: some of these should just be on nonRootParticles + const types = [ + ParsersConstants.popularity, + ParsersConstants.inScope, + ParsersConstants.cells, + ParsersConstants.extends, + ParsersConstants.description, + ParsersConstants.catchAllParser, + ParsersConstants.catchAllCellType, + ParsersConstants.cellParser, + ParsersConstants.extensions, + ParsersConstants.tags, + ParsersConstants.crux, + ParsersConstants.cruxFromId, + ParsersConstants.listDelimiter, + ParsersConstants.contentKey, + ParsersConstants.childrenKey, + ParsersConstants.uniqueFirstWord, + ParsersConstants.uniqueLine, + ParsersConstants.pattern, + ParsersConstants.baseParser, + ParsersConstants.required, + ParsersConstants.root, + ParsersConstants._rootNodeJsHeader, + ParsersConstants.javascript, + ParsersConstants.compilesTo, + ParsersConstants.javascript, + ParsersConstants.single, + ParsersConstants.comment + ] + const map = {} + types.forEach(type => { + map[type] = Particle + }) + map[ParsersConstantsConstantTypes.boolean] = ParsersParserConstantBoolean + map[ParsersConstantsConstantTypes.int] = ParsersParserConstantInt + map[ParsersConstantsConstantTypes.string] = ParsersParserConstantString + map[ParsersConstantsConstantTypes.float] = ParsersParserConstantFloat + map[ParsersConstants.compilerParser] = ParsersCompilerParser + map[ParsersConstants.example] = ParsersExampleParser + return new Particle.ParserCombinator(undefined, map, [{ regex: HandParsersProgram.parserFullRegex, parser: parserDefinitionParser }]) + } + toTypeScriptInterface(used = new Set()) { + let childrenInterfaces = [] + let properties = [] + const inScope = this.firstWordMapWithDefinitions + const thisId = this.id + used.add(thisId) + Object.keys(inScope).forEach(key => { + const def = inScope[key] + const map = def.firstWordMapWithDefinitions + const id = def.id + const optionalTag = def.isRequired() ? "" : "?" + const escapedKey = key.match(/\?/) ? `"${key}"` : key + const description = def.description + if (Object.keys(map).length && !used.has(id)) { + childrenInterfaces.push(def.toTypeScriptInterface(used)) + properties.push(` ${escapedKey}${optionalTag}: ${id}`) + } else properties.push(` ${escapedKey}${optionalTag}: any${description ? " // " + description : ""}`) + }) + properties.sort() + const description = this.description + const myInterface = "" + return `${childrenInterfaces.join("\n")} +${description ? "// " + description : ""} +interface ${thisId} { +${properties.join("\n")} +}`.trim() + } + get id() { + return this.getWord(0) + } + get idWithoutSuffix() { + return this.id.replace(HandParsersProgram.parserSuffixRegex, "") + } + get constantsObject() { + const obj = this._getUniqueConstantParticles() + Object.keys(obj).forEach(key => (obj[key] = obj[key].constantValue)) + return obj + } + _getUniqueConstantParticles(extended = true) { + const obj = {} + const items = extended ? this._getChildrenByParserInExtended(AbstractParserConstantParser) : this.getChildrenByParser(AbstractParserConstantParser) + items.reverse() // Last definition wins. + items.forEach(particle => (obj[particle.identifier] = particle)) + return obj + } + get examples() { + return this._getChildrenByParserInExtended(ParsersExampleParser) + } + get parserIdFromDefinition() { + return this.getWord(0) + } + // todo: remove? just reused parserId + get generatedClassName() { + return this.parserIdFromDefinition + } + _hasValidParserId() { + return !!this.generatedClassName + } + _isAbstract() { + return this.id.startsWith(ParsersConstants.abstractParserPrefix) + } + get cruxIfAny() { + return this.get(ParsersConstants.crux) || (this._hasFromExtended(ParsersConstants.cruxFromId) ? this.idWithoutSuffix : undefined) + } + get regexMatch() { + return this.get(ParsersConstants.pattern) + } + get firstCellEnumOptions() { + const firstCellDef = this._getMyCellTypeDefs()[0] + return firstCellDef ? firstCellDef._getEnumOptions() : undefined + } + get languageDefinitionProgram() { + return this.root + } + get customJavascriptMethods() { + const hasJsCode = this.has(ParsersConstants.javascript) + return hasJsCode ? this.getParticle(ParsersConstants.javascript).childrenToString() : "" + } + get firstWordMapWithDefinitions() { + if (!this._cache_firstWordToParticleDefMap) this._cache_firstWordToParticleDefMap = this._createParserInfo(this._getInScopeParserIds()).firstWordMap + return this._cache_firstWordToParticleDefMap + } + // todo: remove + get runTimeFirstWordsInScope() { + return this._getParser().getFirstWordOptions() + } + _getMyCellTypeDefs() { + const requiredCells = this.get(ParsersConstants.cells) + if (!requiredCells) return [] + const parsersProgram = this.languageDefinitionProgram + return requiredCells.split(" ").map(cellTypeId => { + const cellTypeDef = parsersProgram.getCellTypeDefinitionById(cellTypeId) + if (!cellTypeDef) throw new Error(`No cellType "${cellTypeId}" found`) + return cellTypeDef + }) + } + // todo: what happens when you have a cell getter and constant with same name? + get cellGettersAndParserConstants() { + // todo: add cellType parsings + const parsersProgram = this.languageDefinitionProgram + const getters = this._getMyCellTypeDefs().map((cellTypeDef, index) => cellTypeDef.getGetter(index)) + const catchAllCellTypeId = this.get(ParsersConstants.catchAllCellType) + if (catchAllCellTypeId) getters.push(parsersProgram.getCellTypeDefinitionById(catchAllCellTypeId).getCatchAllGetter(getters.length)) + // Constants + Object.values(this._getUniqueConstantParticles(false)).forEach(particle => getters.push(particle.getGetter())) + return getters.join("\n") + } + _createParserInfo(parserIdsInScope) { + const result = { + firstWordMap: {}, + regexTests: [] + } + if (!parserIdsInScope.length) return result + const allProgramParserDefinitionsMap = this.programParserDefinitionCache + Object.keys(allProgramParserDefinitionsMap) + .filter(parserId => { + const def = allProgramParserDefinitionsMap[parserId] + return def.isOrExtendsAParserInScope(parserIdsInScope) && !def._isAbstract() + }) + .forEach(parserId => { + const def = allProgramParserDefinitionsMap[parserId] + const regex = def.regexMatch + const crux = def.cruxIfAny + const enumOptions = def.firstCellEnumOptions + if (regex) result.regexTests.push({ regex: regex, parser: def.parserIdFromDefinition }) + else if (crux) result.firstWordMap[crux] = def + else if (enumOptions) { + enumOptions.forEach(option => (result.firstWordMap[option] = def)) + } + }) + return result + } + get topParserDefinitions() { + const arr = Object.values(this.firstWordMapWithDefinitions) + arr.sort(Utils.makeSortByFn(definition => definition.popularity)) + arr.reverse() + return arr + } + _getMyInScopeParserIds(target = this) { + const parsersParticle = target.getParticle(ParsersConstants.inScope) + const scopedDefinitionIds = target.myScopedParserDefinitions.map(def => def.id) + return parsersParticle ? parsersParticle.getWordsFrom(1).concat(scopedDefinitionIds) : scopedDefinitionIds + } + _getInScopeParserIds() { + // todo: allow multiple of these if we allow mixins? + const ids = this._getMyInScopeParserIds() + const parentDef = this._getExtendedParent() + return parentDef ? ids.concat(parentDef._getInScopeParserIds()) : ids + } + get isSingle() { + const hit = this._getParticleFromExtended(ParsersConstants.single) + return hit && hit.get(ParsersConstants.single) !== "false" + } + get isUniqueLine() { + const hit = this._getParticleFromExtended(ParsersConstants.uniqueLine) + return hit && hit.get(ParsersConstants.uniqueLine) !== "false" + } + isRequired() { + return this._hasFromExtended(ParsersConstants.required) + } + getParserDefinitionByParserId(parserId) { + // todo: return catch all? + const def = this.programParserDefinitionCache[parserId] + if (def) return def + this.languageDefinitionProgram._addDefaultCatchAllBlobParser() // todo: cleanup. Why did I do this? Needs to be removed or documented. + const particleDef = this.languageDefinitionProgram.programParserDefinitionCache[parserId] + if (!particleDef) throw new Error(`No definition found for parser id "${parserId}". Particle: \n---\n${this.asString}\n---`) + return particleDef + } + isDefined(parserId) { + return !!this.programParserDefinitionCache[parserId] + } + get idToParticleMap() { + return this.programParserDefinitionCache + } + _amIRoot() { + if (this._cache_isRoot === undefined) this._cache_isRoot = this._languageRootParticle === this + return this._cache_isRoot + } + get _languageRootParticle() { + return this.root.rootParserDefinition + } + _isErrorParser() { + return this.get(ParsersConstants.baseParser) === ParsersConstants.errorParser + } + _isBlobParser() { + // Do not check extended classes. Only do once. + return this._getFromExtended(ParsersConstants.baseParser) === ParsersConstants.blobParser + } + get errorMethodToJavascript() { + if (this._isBlobParser()) return "getErrors() { return [] }" // Skips parsing child particles for perf gains. + if (this._isErrorParser()) return "getErrors() { return this._getErrorParserErrors() }" + return "" + } + get parserAsJavascript() { + if (this._isBlobParser()) + // todo: do we need this? + return "createParserCombinator() { return new Particle.ParserCombinator(this._getBlobParserCatchAllParser())}" + const parserInfo = this._createParserInfo(this._getMyInScopeParserIds()) + const myFirstWordMap = parserInfo.firstWordMap + const regexRules = parserInfo.regexTests + // todo: use constants in first word maps? + // todo: cache the super extending? + const firstWords = Object.keys(myFirstWordMap) + const hasFirstWords = firstWords.length + const catchAllParser = this.catchAllParserToJavascript + if (!hasFirstWords && !catchAllParser && !regexRules.length) return "" + const firstWordsStr = hasFirstWords + ? `Object.assign(Object.assign({}, super.createParserCombinator()._getFirstWordMapAsObject()), {` + firstWords.map(firstWord => `"${firstWord}" : ${myFirstWordMap[firstWord].parserIdFromDefinition}`).join(",\n") + "})" + : "undefined" + const regexStr = regexRules.length + ? `[${regexRules + .map(rule => { + return `{regex: /${rule.regex}/, parser: ${rule.parser}}` + }) + .join(",")}]` + : "undefined" + const catchAllStr = catchAllParser ? catchAllParser : this._amIRoot() ? `this._getBlobParserCatchAllParser()` : "undefined" + const scopedParserJavascript = this.myScopedParserDefinitions.map(def => def.asJavascriptClass).join("\n\n") + return `createParserCombinator() {${scopedParserJavascript} + return new Particle.ParserCombinator(${catchAllStr}, ${firstWordsStr}, ${regexStr}) + }` + } + get myScopedParserDefinitions() { + return this.getChildrenByParser(parserDefinitionParser) + } + get catchAllParserToJavascript() { + if (this._isBlobParser()) return "this._getBlobParserCatchAllParser()" + const parserId = this.get(ParsersConstants.catchAllParser) + if (!parserId) return "" + const particleDef = this.getParserDefinitionByParserId(parserId) + return particleDef.generatedClassName + } + get asJavascriptClass() { + const components = [this.parserAsJavascript, this.errorMethodToJavascript, this.cellGettersAndParserConstants, this.customJavascriptMethods].filter(identity => identity) + const thisClassName = this.generatedClassName + if (this._amIRoot()) { + components.push(`static cachedHandParsersProgramRoot = new HandParsersProgram(\`${Utils.escapeBackTicks(this.parent.toString().replace(/\\/g, "\\\\"))}\`) + get handParsersProgram() { + return this.constructor.cachedHandParsersProgramRoot + }`) + components.push(`static rootParser = ${thisClassName}`) + } + return `class ${thisClassName} extends ${this._getExtendsClassName()} { + ${components.join("\n")} + }` + } + _getExtendsClassName() { + const extendedDef = this._getExtendedParent() + return extendedDef ? extendedDef.generatedClassName : "ParserBackedParticle" + } + _getCompilerObject() { + let obj = {} + const items = this._getChildrenByParserInExtended(ParsersCompilerParser) + items.reverse() // Last definition wins. + items.forEach(particle => { + obj = Object.assign(obj, particle.toObject()) // todo: what about multiline strings? + }) + return obj + } + // todo: improve layout (use bold?) + get lineHints() { + return this.cellParser.lineHints + } + isOrExtendsAParserInScope(firstWordsInScope) { + const chain = this._getParserInheritanceSet() + return firstWordsInScope.some(firstWord => chain.has(firstWord)) + } + isTerminalParser() { + return !this._getFromExtended(ParsersConstants.inScope) && !this._getFromExtended(ParsersConstants.catchAllParser) + } + get sublimeMatchLine() { + const regexMatch = this.regexMatch + if (regexMatch) return `'${regexMatch}'` + const cruxMatch = this.cruxIfAny + if (cruxMatch) return `'^ *${Utils.escapeRegExp(cruxMatch)}(?: |$)'` + const enumOptions = this.firstCellEnumOptions + if (enumOptions) return `'^ *(${Utils.escapeRegExp(enumOptions.join("|"))})(?: |$)'` + } + // todo: refactor. move some parts to cellParser? + _toSublimeMatchBlock() { + const defaultPaint = "source" + const program = this.languageDefinitionProgram + const cellParser = this.cellParser + const requiredCellTypeIds = cellParser.getRequiredCellTypeIds() + const catchAllCellTypeId = cellParser.catchAllCellTypeId + const firstCellTypeDef = program.getCellTypeDefinitionById(requiredCellTypeIds[0]) + const firstWordPaint = (firstCellTypeDef ? firstCellTypeDef.paint : defaultPaint) + "." + this.parserIdFromDefinition + const topHalf = ` '${this.parserIdFromDefinition}': + - match: ${this.sublimeMatchLine} + scope: ${firstWordPaint}` + if (catchAllCellTypeId) requiredCellTypeIds.push(catchAllCellTypeId) + if (!requiredCellTypeIds.length) return topHalf + const captures = requiredCellTypeIds + .map((cellTypeId, index) => { + const cellTypeDefinition = program.getCellTypeDefinitionById(cellTypeId) // todo: cleanup + if (!cellTypeDefinition) throw new Error(`No ${ParsersConstants.cellType} ${cellTypeId} found`) // todo: standardize error/capture error at parsers time + return ` ${index + 1}: ${(cellTypeDefinition.paint || defaultPaint) + "." + cellTypeDefinition.cellTypeId}` + }) + .join("\n") + const cellTypesToRegex = cellTypeIds => cellTypeIds.map(cellTypeId => `({{${cellTypeId}}})?`).join(" ?") + return `${topHalf} + push: + - match: ${cellTypesToRegex(requiredCellTypeIds)} + captures: +${captures} + - match: $ + pop: true` + } + _getParserInheritanceSet() { + if (!this._cache_parserInheritanceSet) this._cache_parserInheritanceSet = new Set(this.ancestorParserIdsArray) + return this._cache_parserInheritanceSet + } + get ancestorParserIdsArray() { + if (!this._cache_ancestorParserIdsArray) { + this._cache_ancestorParserIdsArray = this._getAncestorsArray().map(def => def.parserIdFromDefinition) + this._cache_ancestorParserIdsArray.reverse() + } + return this._cache_ancestorParserIdsArray + } + get programParserDefinitionCache() { + if (!this._cache_parserDefinitionParsers) this._cache_parserDefinitionParsers = this.isRoot || this.hasParserDefinitions ? this.makeProgramParserDefinitionCache() : this.parent.programParserDefinitionCache + return this._cache_parserDefinitionParsers + } + get hasParserDefinitions() { + return !!this.getChildrenByParser(parserDefinitionParser).length + } + makeProgramParserDefinitionCache() { + const scopedParsers = this.getChildrenByParser(parserDefinitionParser) + const cache = Object.assign({}, this.parent.programParserDefinitionCache) // todo. We don't really need this. we should just lookup the parent if no local hits. + scopedParsers.forEach(parserDefinitionParser => (cache[parserDefinitionParser.parserIdFromDefinition] = parserDefinitionParser)) + return cache + } + get description() { + return this._getFromExtended(ParsersConstants.description) || "" + } + get popularity() { + const val = this._getFromExtended(ParsersConstants.popularity) + return val ? parseFloat(val) : 0 + } + _getExtendedParserId() { + const ancestorIds = this.ancestorParserIdsArray + if (ancestorIds.length > 1) return ancestorIds[ancestorIds.length - 2] + } + _toStumpString() { + const crux = this.cruxIfAny + const cellArray = this.cellParser.getCellArray().filter((item, index) => index) // for now this only works for keyword langs + if (!cellArray.length) + // todo: remove this! just doing it for now until we refactor getCellArray to handle catchAlls better. + return "" + const cells = new Particle(cellArray.map((cell, index) => cell._toStumpInput(crux)).join("\n")) + return `div + label ${crux} +${cells.toString(1)}` + } + toStumpString() { + const particleBreakSymbol = "\n" + return this._getConcreteNonErrorInScopeParticleDefinitions(this._getInScopeParserIds()) + .map(def => def._toStumpString()) + .filter(identity => identity) + .join(particleBreakSymbol) + } + _generateSimulatedLine(seed) { + // todo: generate simulated data from catch all + const crux = this.cruxIfAny + return this.cellParser + .getCellArray() + .map((cell, index) => (!index && crux ? crux : cell.synthesizeCell(seed))) + .join(" ") + } + _shouldSynthesize(def, parserChain) { + if (def._isErrorParser() || def._isAbstract()) return false + if (parserChain.includes(def.id)) return false + const tags = def.get(ParsersConstants.tags) + if (tags && tags.includes(ParsersConstantsMisc.doNotSynthesize)) return false + return true + } + // Get all definitions in this current scope down, even ones that are scoped inside other definitions. + get inScopeAndDescendantDefinitions() { + return this.languageDefinitionProgram._collectAllDefinitions(Object.values(this.programParserDefinitionCache), []) + } + _collectAllDefinitions(defs, collection = []) { + defs.forEach(def => { + collection.push(def) + def._collectAllDefinitions(def.getChildrenByParser(parserDefinitionParser), collection) + }) + return collection + } + get cruxPath() { + const parentPath = this.parent.cruxPath + return (parentPath ? parentPath + " " : "") + this.cruxIfAny + } + get cruxPathAsColumnName() { + return this.cruxPath.replace(/ /g, "_") + } + // Get every definition that extends from this one, even ones that are scoped inside other definitions. + get concreteDescendantDefinitions() { + const { inScopeAndDescendantDefinitions, id } = this + return Object.values(inScopeAndDescendantDefinitions).filter(def => def._doesExtend(id) && !def._isAbstract()) + } + get concreteInScopeDescendantDefinitions() { + // Note: non-recursive. + const defs = this.programParserDefinitionCache + const id = this.id + return Object.values(defs).filter(def => def._doesExtend(id) && !def._isAbstract()) + } + _getConcreteNonErrorInScopeParticleDefinitions(parserIds) { + const defs = [] + parserIds.forEach(parserId => { + const def = this.getParserDefinitionByParserId(parserId) + if (def._isErrorParser()) return + else if (def._isAbstract()) def.concreteInScopeDescendantDefinitions.forEach(def => defs.push(def)) + else defs.push(def) + }) + return defs + } + // todo: refactor + synthesizeParticle(particleCount = 1, indentCount = -1, parsersAlreadySynthesized = [], seed = Date.now()) { + let inScopeParserIds = this._getInScopeParserIds() + const catchAllParserId = this._getFromExtended(ParsersConstants.catchAllParser) + if (catchAllParserId) inScopeParserIds.push(catchAllParserId) + const thisId = this.id + if (!parsersAlreadySynthesized.includes(thisId)) parsersAlreadySynthesized.push(thisId) + const lines = [] + while (particleCount) { + const line = this._generateSimulatedLine(seed) + if (line) lines.push(" ".repeat(indentCount >= 0 ? indentCount : 0) + line) + this._getConcreteNonErrorInScopeParticleDefinitions(inScopeParserIds.filter(parserId => !parsersAlreadySynthesized.includes(parserId))) + .filter(def => this._shouldSynthesize(def, parsersAlreadySynthesized)) + .forEach(def => { + const chain = parsersAlreadySynthesized // .slice(0) + chain.push(def.id) + def.synthesizeParticle(1, indentCount + 1, chain, seed).forEach(line => lines.push(line)) + }) + particleCount-- + } + return lines + } + get cellParser() { + if (!this._cellParser) { + const cellParsingStrategy = this._getFromExtended(ParsersConstants.cellParser) + if (cellParsingStrategy === ParsersCellParser.postfix) this._cellParser = new PostfixCellParser(this) + else if (cellParsingStrategy === ParsersCellParser.omnifix) this._cellParser = new OmnifixCellParser(this) + else this._cellParser = new PrefixCellParser(this) + } + return this._cellParser + } +} +// todo: remove? +class parserDefinitionParser extends AbstractParserDefinitionParser {} +// HandParsersProgram is a constructor that takes a parsers file, and builds a new +// constructor for new language that takes files in that language to execute, compile, etc. +class HandParsersProgram extends AbstractParserDefinitionParser { + createParserCombinator() { + const map = {} + map[ParsersConstants.comment] = Particle + return new Particle.ParserCombinator(UnknownParserParticle, map, [ + { regex: HandParsersProgram.blankLineRegex, parser: Particle }, + { regex: HandParsersProgram.parserFullRegex, parser: parserDefinitionParser }, + { regex: HandParsersProgram.cellTypeFullRegex, parser: cellTypeDefinitionParser } + ]) + } + // rootParser + // Note: this is some so far unavoidable tricky code. We need to eval the transpiled JS, in a NodeJS or browser environment. + _compileAndReturnRootParser() { + if (this._cache_rootParser) return this._cache_rootParser + if (!this.isNodeJs()) { + this._cache_rootParser = Utils.appendCodeAndReturnValueOnWindow(this.toBrowserJavascript(), this.rootParserId).rootParser + return this._cache_rootParser + } + const path = require("path") + const code = this.toNodeJsJavascript(__dirname) + try { + const rootParticle = this._requireInVmNodeJsRootParser(code) + this._cache_rootParser = rootParticle.rootParser + if (!this._cache_rootParser) throw new Error(`Failed to rootParser`) + } catch (err) { + // todo: figure out best error pattern here for debugging + console.log(err) + // console.log(`Error in code: `) + // console.log(new Particle(code).toStringWithLineNumbers()) + } + return this._cache_rootParser + } + get cruxPath() { + return "" + } + trainModel(programs, rootParser = this.compileAndReturnRootParser()) { + const particleDefs = this.validConcreteAndAbstractParserDefinitions + const particleDefCountIncludingRoot = particleDefs.length + 1 + const matrix = Utils.makeMatrix(particleDefCountIncludingRoot, particleDefCountIncludingRoot, 0) + const idToIndex = {} + const indexToId = {} + particleDefs.forEach((def, index) => { + const id = def.id + idToIndex[id] = index + 1 + indexToId[index + 1] = id + }) + programs.forEach(code => { + const exampleProgram = new rootParser(code) + exampleProgram.topDownArray.forEach(particle => { + const particleIndex = idToIndex[particle.definition.id] + const parentParticle = particle.parent + if (!particleIndex) return undefined + if (parentParticle.isRoot()) matrix[0][particleIndex]++ + else { + const parentIndex = idToIndex[parentParticle.definition.id] + if (!parentIndex) return undefined + matrix[parentIndex][particleIndex]++ + } + }) + }) + return { + idToIndex, + indexToId, + matrix + } + } + _mapPredictions(predictionsVector, model) { + const total = Utils.sum(predictionsVector) + const predictions = predictionsVector.slice(1).map((count, index) => { + const id = model.indexToId[index + 1] + return { + id, + def: this.getParserDefinitionByParserId(id), + count, + prob: count / total + } + }) + predictions.sort(Utils.makeSortByFn(prediction => prediction.count)).reverse() + return predictions + } + predictChildren(model, particle) { + return this._mapPredictions(this._predictChildren(model, particle), model) + } + predictParents(model, particle) { + return this._mapPredictions(this._predictParents(model, particle), model) + } + _predictChildren(model, particle) { + return model.matrix[particle.isRoot() ? 0 : model.idToIndex[particle.definition.id]] + } + _predictParents(model, particle) { + if (particle.isRoot()) return [] + const particleIndex = model.idToIndex[particle.definition.id] + return model.matrix.map(row => row[particleIndex]) + } + _setDirName(name) { + this._dirName = name + return this + } + _requireInVmNodeJsRootParser(code) { + const vm = require("vm") + const path = require("path") + // todo: cleanup up + try { + Object.keys(GlobalNamespaceAdditions).forEach(key => { + global[key] = require("./" + GlobalNamespaceAdditions[key]) + }) + global.require = require + global.__dirname = this._dirName + global.module = {} + return vm.runInThisContext(code) + } catch (err) { + // todo: figure out best error pattern here for debugging + console.log(`Error in compiled parsers code for language "${this.parsersName}"`) + // console.log(new Particle(code).toStringWithLineNumbers()) + console.log(err) + throw err + } + } + examplesToTestBlocks(rootParser = this.compileAndReturnRootParser(), expectedErrorMessage = "") { + const testBlocks = {} + this.validConcreteAndAbstractParserDefinitions.forEach(def => + def.examples.forEach(example => { + const id = def.id + example.content + testBlocks[id] = equal => { + const exampleProgram = new rootParser(example.childrenToString()) + const errors = exampleProgram.getAllErrors(example._getLineNumber() + 1) + equal(errors.join("\n"), expectedErrorMessage, `Expected no errors in ${id}`) + } + }) + ) + return testBlocks + } + toReadMe() { + const languageName = this.extensionName + const rootParticleDef = this.rootParserDefinition + const cellTypes = this.cellTypeDefinitions + const parserLineage = this.parserLineage + const exampleParticle = rootParticleDef.examples[0] + return `title ${languageName} Readme + +paragraph ${rootParticleDef.description} + +subtitle Quick Example + +code +${exampleParticle ? exampleParticle.childrenToString(1) : ""} + +subtitle Quick facts about ${languageName} + +list + - ${languageName} has ${parserLineage.topDownArray.length} particle types. + - ${languageName} has ${Object.keys(cellTypes).length} cell types + - The source code for ${languageName} is ${this.topDownArray.length} lines long. + +subtitle Installing + +code + npm install . + +subtitle Testing + +code + node test.js + +subtitle Parsers + +code +${parserLineage.toString(1)} + +subtitle Cell Types + +code +${new Particle(Object.keys(cellTypes).join("\n")).toString(1)} + +subtitle Road Map + +paragraph Here are the "todos" present in the source code for ${languageName}: + +list +${this.topDownArray + .filter(particle => particle.getWord(0) === "todo") + .map(particle => ` - ${particle.getLine()}`) + .join("\n")} + +paragraph This readme was auto-generated using the + link https://github.com/breck7/scrollsdk ScrollSDK.` + } + toBundle() { + const files = {} + const rootParticleDef = this.rootParserDefinition + const languageName = this.extensionName + const example = rootParticleDef.examples[0] + const sampleCode = example ? example.childrenToString() : "" + files[ParsersBundleFiles.package] = JSON.stringify( + { + name: languageName, + private: true, + dependencies: { + scrollsdk: Particle.getVersion() + } + }, + null, + 2 + ) + files[ParsersBundleFiles.readme] = this.toReadMe() + const testCode = `const program = new ${languageName}(sampleCode) +const errors = program.getAllErrors() +console.log("Sample program compiled with " + errors.length + " errors.") +if (errors.length) + console.log(errors.map(error => error.message))` + const nodePath = `${languageName}.node.js` + files[nodePath] = this.toNodeJsJavascript() + files[ParsersBundleFiles.indexJs] = `module.exports = require("./${nodePath}")` + const browserPath = `${languageName}.browser.js` + files[browserPath] = this.toBrowserJavascript() + files[ParsersBundleFiles.indexHtml] = ` + + + +` + const samplePath = "sample." + this.extensionName + files[samplePath] = sampleCode.toString() + files[ParsersBundleFiles.testJs] = `const ${languageName} = require("./index.js") +/*keep-line*/ const sampleCode = require("fs").readFileSync("${samplePath}", "utf8") +${testCode}` + return files + } + get targetExtension() { + return this.rootParserDefinition.get(ParsersConstants.compilesTo) + } + get cellTypeDefinitions() { + if (this._cache_cellTypes) return this._cache_cellTypes + const types = {} + // todo: add built in word types? + this.getChildrenByParser(cellTypeDefinitionParser).forEach(type => (types[type.cellTypeId] = type)) + this._cache_cellTypes = types + return types + } + getCellTypeDefinitionById(cellTypeId) { + // todo: return unknownCellTypeDefinition? or is that handled somewhere else? + return this.cellTypeDefinitions[cellTypeId] + } + get parserLineage() { + const newParticle = new Particle() + Object.values(this.validConcreteAndAbstractParserDefinitions).forEach(particle => newParticle.touchParticle(particle.ancestorParserIdsArray.join(" "))) + return newParticle + } + get languageDefinitionProgram() { + return this + } + get validConcreteAndAbstractParserDefinitions() { + return this.getChildrenByParser(parserDefinitionParser).filter(particle => particle._hasValidParserId()) + } + get lastRootParserDefinitionParticle() { + return this.findLast(def => def instanceof AbstractParserDefinitionParser && def.has(ParsersConstants.root) && def._hasValidParserId()) + } + _initRootParserDefinitionParticle() { + if (this._cache_rootParserParticle) return + if (!this._cache_rootParserParticle) this._cache_rootParserParticle = this.lastRootParserDefinitionParticle + // By default, have a very permissive basic root particle. + // todo: whats the best design pattern to use for this sort of thing? + if (!this._cache_rootParserParticle) { + this._cache_rootParserParticle = this.concat(`${ParsersConstants.DefaultRootParser} + ${ParsersConstants.root} + ${ParsersConstants.catchAllParser} ${ParsersConstants.BlobParser}`)[0] + this._addDefaultCatchAllBlobParser() + } + } + get rootParserDefinition() { + this._initRootParserDefinitionParticle() + return this._cache_rootParserParticle + } + _addDefaultCatchAllBlobParser() { + if (this._addedCatchAll) return + this._addedCatchAll = true + delete this._cache_parserDefinitionParsers + this.concat(`${ParsersConstants.BlobParser} + ${ParsersConstants.baseParser} ${ParsersConstants.blobParser}`) + } + get extensionName() { + return this.parsersName + } + get id() { + return this.rootParserId + } + get rootParserId() { + return this.rootParserDefinition.parserIdFromDefinition + } + get parsersName() { + return this.rootParserId.replace(HandParsersProgram.parserSuffixRegex, "") + } + _getMyInScopeParserIds() { + return super._getMyInScopeParserIds(this.rootParserDefinition) + } + _getInScopeParserIds() { + const parsersParticle = this.rootParserDefinition.getParticle(ParsersConstants.inScope) + return parsersParticle ? parsersParticle.getWordsFrom(1) : [] + } + makeProgramParserDefinitionCache() { + const cache = {} + this.getChildrenByParser(parserDefinitionParser).forEach(parserDefinitionParser => (cache[parserDefinitionParser.parserIdFromDefinition] = parserDefinitionParser)) + return cache + } + compileAndReturnRootParser() { + if (!this._cached_rootParser) { + const rootDef = this.rootParserDefinition + this._cached_rootParser = rootDef.languageDefinitionProgram._compileAndReturnRootParser() + } + return this._cached_rootParser + } + get fileExtensions() { + return this.rootParserDefinition.get(ParsersConstants.extensions) ? this.rootParserDefinition.get(ParsersConstants.extensions).split(" ").join(",") : this.extensionName + } + toNodeJsJavascript(scrollsdkProductsPath = "scrollsdk/products") { + return this._rootParticleDefToJavascriptClass(scrollsdkProductsPath, true).trim() + } + toBrowserJavascript() { + return this._rootParticleDefToJavascriptClass("", false).trim() + } + _rootParticleDefToJavascriptClass(scrollsdkProductsPath, forNodeJs = true) { + const defs = this.validConcreteAndAbstractParserDefinitions + // todo: throw if there is no root particle defined + const parserClasses = defs.map(def => def.asJavascriptClass).join("\n\n") + const rootDef = this.rootParserDefinition + const rootNodeJsHeader = forNodeJs && rootDef._getConcatBlockStringFromExtended(ParsersConstants._rootNodeJsHeader) + const rootName = rootDef.generatedClassName + if (!rootName) throw new Error(`Root Particle Type Has No Name`) + let exportScript = "" + if (forNodeJs) + exportScript = `module.exports = ${rootName}; +${rootName}` + else exportScript = `window.${rootName} = ${rootName}` + let nodeJsImports = `` + if (forNodeJs) { + const path = require("path") + nodeJsImports = Object.keys(GlobalNamespaceAdditions) + .map(key => { + const thePath = scrollsdkProductsPath + "/" + GlobalNamespaceAdditions[key] + return `const { ${key} } = require("${thePath.replace(/\\/g, "\\\\")}")` // escape windows backslashes + }) + .join("\n") + } + // todo: we can expose the previous "constants" export, if needed, via the parsers, which we preserve. + return `{ +${nodeJsImports} +${rootNodeJsHeader ? rootNodeJsHeader : ""} +${parserClasses} + +${exportScript} +} +` + } + toSublimeSyntaxFile() { + const cellTypeDefs = this.cellTypeDefinitions + const variables = Object.keys(cellTypeDefs) + .map(name => ` ${name}: '${cellTypeDefs[name].regexString}'`) + .join("\n") + const defs = this.validConcreteAndAbstractParserDefinitions.filter(kw => !kw._isAbstract()) + const parserContexts = defs.map(def => def._toSublimeMatchBlock()).join("\n\n") + const includes = defs.map(parserDef => ` - include: '${parserDef.parserIdFromDefinition}'`).join("\n") + return `%YAML 1.2 +--- +name: ${this.extensionName} +file_extensions: [${this.fileExtensions}] +scope: source.${this.extensionName} + +variables: +${variables} + +contexts: + main: +${includes} + +${parserContexts}` + } +} +HandParsersProgram.makeParserId = str => Utils._replaceNonAlphaNumericCharactersWithCharCodes(str).replace(HandParsersProgram.parserSuffixRegex, "") + ParsersConstants.parserSuffix +HandParsersProgram.makeCellTypeId = str => Utils._replaceNonAlphaNumericCharactersWithCharCodes(str).replace(HandParsersProgram.cellTypeSuffixRegex, "") + ParsersConstants.cellTypeSuffix +HandParsersProgram.parserSuffixRegex = new RegExp(ParsersConstants.parserSuffix + "$") +HandParsersProgram.parserFullRegex = new RegExp("^[a-zA-Z0-9_]+" + ParsersConstants.parserSuffix + "$") +HandParsersProgram.blankLineRegex = new RegExp("^$") +HandParsersProgram.cellTypeSuffixRegex = new RegExp(ParsersConstants.cellTypeSuffix + "$") +HandParsersProgram.cellTypeFullRegex = new RegExp("^[a-zA-Z0-9_]+" + ParsersConstants.cellTypeSuffix + "$") +HandParsersProgram._languages = {} +HandParsersProgram._parsers = {} +const PreludeKinds = {} +PreludeKinds[PreludeCellTypeIds.anyCell] = ParsersAnyCell +PreludeKinds[PreludeCellTypeIds.keywordCell] = ParsersKeywordCell +PreludeKinds[PreludeCellTypeIds.floatCell] = ParsersFloatCell +PreludeKinds[PreludeCellTypeIds.numberCell] = ParsersFloatCell +PreludeKinds[PreludeCellTypeIds.bitCell] = ParsersBitCell +PreludeKinds[PreludeCellTypeIds.boolCell] = ParsersBoolCell +PreludeKinds[PreludeCellTypeIds.intCell] = ParsersIntCell +class UnknownParsersProgram extends Particle { + _inferRootParticleForAPrefixLanguage(parsersName) { + parsersName = HandParsersProgram.makeParserId(parsersName) + const rootParticle = new Particle(`${parsersName} + ${ParsersConstants.root}`) + // note: right now we assume 1 global cellTypeMap and parserMap per parsers. But we may have scopes in the future? + const rootParticleNames = this.getFirstWords() + .filter(identity => identity) + .map(word => HandParsersProgram.makeParserId(word)) + rootParticle + .particleAt(0) + .touchParticle(ParsersConstants.inScope) + .setWordsFrom(1, Array.from(new Set(rootParticleNames))) + return rootParticle + } + _renameIntegerKeywords(clone) { + // todo: why are we doing this? + for (let particle of clone.getTopDownArrayIterator()) { + const firstWordIsAnInteger = !!particle.firstWord.match(/^\d+$/) + const parentFirstWord = particle.parent.firstWord + if (firstWordIsAnInteger && parentFirstWord) particle.setFirstWord(HandParsersProgram.makeParserId(parentFirstWord + UnknownParsersProgram._childSuffix)) + } + } + _getKeywordMaps(clone) { + const keywordsToChildKeywords = {} + const keywordsToParticleInstances = {} + for (let particle of clone.getTopDownArrayIterator()) { + const firstWord = particle.firstWord + if (!keywordsToChildKeywords[firstWord]) keywordsToChildKeywords[firstWord] = {} + if (!keywordsToParticleInstances[firstWord]) keywordsToParticleInstances[firstWord] = [] + keywordsToParticleInstances[firstWord].push(particle) + particle.forEach(child => (keywordsToChildKeywords[firstWord][child.firstWord] = true)) + } + return { keywordsToChildKeywords: keywordsToChildKeywords, keywordsToParticleInstances: keywordsToParticleInstances } + } + _inferParserDef(firstWord, globalCellTypeMap, childFirstWords, instances) { + const edgeSymbol = this.edgeSymbol + const parserId = HandParsersProgram.makeParserId(firstWord) + const particleDefParticle = new Particle(parserId).particleAt(0) + const childParserIds = childFirstWords.map(word => HandParsersProgram.makeParserId(word)) + if (childParserIds.length) particleDefParticle.touchParticle(ParsersConstants.inScope).setWordsFrom(1, childParserIds) + const cellsForAllInstances = instances + .map(line => line.content) + .filter(identity => identity) + .map(line => line.split(edgeSymbol)) + const instanceCellCounts = new Set(cellsForAllInstances.map(cells => cells.length)) + const maxCellsOnLine = Math.max(...Array.from(instanceCellCounts)) + const minCellsOnLine = Math.min(...Array.from(instanceCellCounts)) + let catchAllCellType + let cellTypeIds = [] + for (let cellIndex = 0; cellIndex < maxCellsOnLine; cellIndex++) { + const cellType = this._getBestCellType( + firstWord, + instances.length, + maxCellsOnLine, + cellsForAllInstances.map(cells => cells[cellIndex]) + ) + if (!globalCellTypeMap.has(cellType.cellTypeId)) globalCellTypeMap.set(cellType.cellTypeId, cellType.cellTypeDefinition) + cellTypeIds.push(cellType.cellTypeId) + } + if (maxCellsOnLine > minCellsOnLine) { + //columns = columns.slice(0, min) + catchAllCellType = cellTypeIds.pop() + while (cellTypeIds[cellTypeIds.length - 1] === catchAllCellType) { + cellTypeIds.pop() + } + } + const needsCruxProperty = !firstWord.endsWith(UnknownParsersProgram._childSuffix + ParsersConstants.parserSuffix) // todo: cleanup + if (needsCruxProperty) particleDefParticle.set(ParsersConstants.crux, firstWord) + if (catchAllCellType) particleDefParticle.set(ParsersConstants.catchAllCellType, catchAllCellType) + const cellLine = cellTypeIds.slice() + cellLine.unshift(PreludeCellTypeIds.keywordCell) + if (cellLine.length > 0) particleDefParticle.set(ParsersConstants.cells, cellLine.join(edgeSymbol)) + //if (!catchAllCellType && cellTypeIds.length === 1) particleDefParticle.set(ParsersConstants.cells, cellTypeIds[0]) + // Todo: add conditional frequencies + return particleDefParticle.parent.toString() + } + // inferParsersFileForAnSSVLanguage(parsersName: string): string { + // parsersName = HandParsersProgram.makeParserId(parsersName) + // const rootParticle = new Particle(`${parsersName} + // ${ParsersConstants.root}`) + // // note: right now we assume 1 global cellTypeMap and parserMap per parsers. But we may have scopes in the future? + // const rootParticleNames = this.getFirstWords().map(word => HandParsersProgram.makeParserId(word)) + // rootParticle + // .particleAt(0) + // .touchParticle(ParsersConstants.inScope) + // .setWordsFrom(1, Array.from(new Set(rootParticleNames))) + // return rootParticle + // } + inferParsersFileForAKeywordLanguage(parsersName) { + const clone = this.clone() + this._renameIntegerKeywords(clone) + const { keywordsToChildKeywords, keywordsToParticleInstances } = this._getKeywordMaps(clone) + const globalCellTypeMap = new Map() + globalCellTypeMap.set(PreludeCellTypeIds.keywordCell, undefined) + const parserDefs = Object.keys(keywordsToChildKeywords) + .filter(identity => identity) + .map(firstWord => this._inferParserDef(firstWord, globalCellTypeMap, Object.keys(keywordsToChildKeywords[firstWord]), keywordsToParticleInstances[firstWord])) + const cellTypeDefs = [] + globalCellTypeMap.forEach((def, id) => cellTypeDefs.push(def ? def : id)) + const particleBreakSymbol = this.particleBreakSymbol + return this._formatCode([this._inferRootParticleForAPrefixLanguage(parsersName).toString(), cellTypeDefs.join(particleBreakSymbol), parserDefs.join(particleBreakSymbol)].filter(identity => identity).join("\n")) + } + _formatCode(code) { + // todo: make this run in browser too + if (!this.isNodeJs()) return code + const parsersProgram = new HandParsersProgram(Particle.fromDisk(__dirname + "/../langs/parsers/parsers.parsers")) + const rootParser = parsersProgram.compileAndReturnRootParser() + const program = new rootParser(code) + return program.format().toString() + } + _getBestCellType(firstWord, instanceCount, maxCellsOnLine, allValues) { + const asSet = new Set(allValues) + const edgeSymbol = this.edgeSymbol + const values = Array.from(asSet).filter(identity => identity) + const every = fn => { + for (let index = 0; index < values.length; index++) { + if (!fn(values[index])) return false + } + return true + } + if (every(str => str === "0" || str === "1")) return { cellTypeId: PreludeCellTypeIds.bitCell } + if ( + every(str => { + const num = parseInt(str) + if (isNaN(num)) return false + return num.toString() === str + }) + ) { + return { cellTypeId: PreludeCellTypeIds.intCell } + } + if (every(str => str.match(/^-?\d*.?\d+$/))) return { cellTypeId: PreludeCellTypeIds.floatCell } + const bools = new Set(["1", "0", "true", "false", "t", "f", "yes", "no"]) + if (every(str => bools.has(str.toLowerCase()))) return { cellTypeId: PreludeCellTypeIds.boolCell } + // todo: cleanup + const enumLimit = 30 + if (instanceCount > 1 && maxCellsOnLine === 1 && allValues.length > asSet.size && asSet.size < enumLimit) + return { + cellTypeId: HandParsersProgram.makeCellTypeId(firstWord), + cellTypeDefinition: `${HandParsersProgram.makeCellTypeId(firstWord)} + enum ${values.join(edgeSymbol)}` + } + return { cellTypeId: PreludeCellTypeIds.anyCell } + } +} +UnknownParsersProgram._childSuffix = "Child" +window.ParsersConstants = ParsersConstants +window.PreludeCellTypeIds = PreludeCellTypeIds +window.HandParsersProgram = HandParsersProgram +window.ParserBackedParticle = ParserBackedParticle +window.UnknownParserError = UnknownParserError +window.UnknownParsersProgram = UnknownParsersProgram + + +"use strict" +// Adapted from https://github.com/NeekSandhu/codemirror-textmate/blob/master/src/tmToCm.ts +var CmToken +;(function (CmToken) { + CmToken["Atom"] = "atom" + CmToken["Attribute"] = "attribute" + CmToken["Bracket"] = "bracket" + CmToken["Builtin"] = "builtin" + CmToken["Comment"] = "comment" + CmToken["Def"] = "def" + CmToken["Error"] = "error" + CmToken["Header"] = "header" + CmToken["HR"] = "hr" + CmToken["Keyword"] = "keyword" + CmToken["Link"] = "link" + CmToken["Meta"] = "meta" + CmToken["Number"] = "number" + CmToken["Operator"] = "operator" + CmToken["Property"] = "property" + CmToken["Qualifier"] = "qualifier" + CmToken["Quote"] = "quote" + CmToken["String"] = "string" + CmToken["String2"] = "string-2" + CmToken["Tag"] = "tag" + CmToken["Type"] = "type" + CmToken["Variable"] = "variable" + CmToken["Variable2"] = "variable-2" + CmToken["Variable3"] = "variable-3" +})(CmToken || (CmToken = {})) +const tmToCm = { + comment: { + $: CmToken.Comment + }, + constant: { + // TODO: Revision + $: CmToken.Def, + character: { + escape: { + $: CmToken.String2 + } + }, + language: { + $: CmToken.Atom + }, + numeric: { + $: CmToken.Number + }, + other: { + email: { + link: { + $: CmToken.Link + } + }, + symbol: { + // TODO: Revision + $: CmToken.Def + } + } + }, + entity: { + name: { + class: { + $: CmToken.Def + }, + function: { + $: CmToken.Def + }, + tag: { + $: CmToken.Tag + }, + type: { + $: CmToken.Type, + class: { + $: CmToken.Variable + } + } + }, + other: { + "attribute-name": { + $: CmToken.Attribute + }, + "inherited-class": { + // TODO: Revision + $: CmToken.Def + } + }, + support: { + function: { + // TODO: Revision + $: CmToken.Def + } + } + }, + invalid: { + $: CmToken.Error, + illegal: { $: CmToken.Error }, + deprecated: { + $: CmToken.Error + } + }, + keyword: { + $: CmToken.Keyword, + operator: { + $: CmToken.Operator + }, + other: { + "special-method": CmToken.Def + } + }, + punctuation: { + $: CmToken.Operator, + definition: { + comment: { + $: CmToken.Comment + }, + tag: { + $: CmToken.Bracket + } + // 'template-expression': { + // $: CodeMirrorToken.Operator, + // }, + } + // terminator: { + // $: CodeMirrorToken.Operator, + // }, + }, + storage: { + $: CmToken.Keyword + }, + string: { + $: CmToken.String, + regexp: { + $: CmToken.String2 + } + }, + support: { + class: { + $: CmToken.Def + }, + constant: { + $: CmToken.Variable2 + }, + function: { + $: CmToken.Def + }, + type: { + $: CmToken.Type + }, + variable: { + $: CmToken.Variable2, + property: { + $: CmToken.Property + } + } + }, + variable: { + $: CmToken.Def, + language: { + // TODO: Revision + $: CmToken.Variable3 + }, + other: { + object: { + $: CmToken.Variable, + property: { + $: CmToken.Property + } + }, + property: { + $: CmToken.Property + } + }, + parameter: { + $: CmToken.Def + } + } +} +const textMateScopeToCodeMirrorStyle = (scopeSegments, style = tmToCm) => { + const matchingBranch = style[scopeSegments.shift()] + return matchingBranch ? textMateScopeToCodeMirrorStyle(scopeSegments, matchingBranch) || matchingBranch.$ || null : null +} +class ParsersCodeMirrorMode { + constructor(name, getRootParserFn, getProgramCodeFn, codeMirrorLib = undefined) { + this._name = name + this._getRootParserFn = getRootParserFn + this._getProgramCodeFn = getProgramCodeFn || (instance => (instance ? instance.getValue() : this._originalValue)) + this._codeMirrorLib = codeMirrorLib + } + _getParsedProgram() { + const source = this._getProgramCodeFn(this._cmInstance) || "" + if (!this._cachedProgram || this._cachedSource !== source) { + this._cachedSource = source + this._cachedProgram = new (this._getRootParserFn())(source) + } + return this._cachedProgram + } + _getExcludedIntelliSenseTriggerKeys() { + return { + 8: "backspace", + 9: "tab", + 13: "enter", + 16: "shift", + 17: "ctrl", + 18: "alt", + 19: "pause", + 20: "capslock", + 27: "escape", + 33: "pageup", + 34: "pagedown", + 35: "end", + 36: "home", + 37: "left", + 38: "up", + 39: "right", + 40: "down", + 45: "insert", + 46: "delete", + 91: "left window key", + 92: "right window key", + 93: "select", + 112: "f1", + 113: "f2", + 114: "f3", + 115: "f4", + 116: "f5", + 117: "f6", + 118: "f7", + 119: "f8", + 120: "f9", + 121: "f10", + 122: "f11", + 123: "f12", + 144: "numlock", + 145: "scrolllock" + } + } + token(stream, state) { + return this._advanceStreamAndReturnTokenType(stream, state) + } + fromTextAreaWithAutocomplete(area, options) { + this._originalValue = area.value + const defaultOptions = { + lineNumbers: true, + mode: this._name, + tabSize: 1, + indentUnit: 1, + hintOptions: { + hint: (cmInstance, options) => this.codeMirrorAutocomplete(cmInstance, options) + } + } + Object.assign(defaultOptions, options) + this._cmInstance = this._getCodeMirrorLib().fromTextArea(area, defaultOptions) + this._enableAutoComplete(this._cmInstance) + return this._cmInstance + } + _enableAutoComplete(cmInstance) { + const excludedKeys = this._getExcludedIntelliSenseTriggerKeys() + const codeMirrorLib = this._getCodeMirrorLib() + cmInstance.on("keyup", (cm, event) => { + // https://stackoverflow.com/questions/13744176/codemirror-autocomplete-after-any-keyup + if (!cm.state.completionActive && !excludedKeys[event.keyCode.toString()]) + // Todo: get typings for CM autocomplete + codeMirrorLib.commands.autocomplete(cm, null, { completeSingle: false }) + }) + } + _getCodeMirrorLib() { + return this._codeMirrorLib + } + async codeMirrorAutocomplete(cmInstance, options) { + const cursor = cmInstance.getDoc().getCursor() + const codeMirrorLib = this._getCodeMirrorLib() + const result = await this._getParsedProgram().getAutocompleteResultsAt(cursor.line, cursor.ch) + // It seems to be better UX if there's only 1 result, and its the word the user entered, to close autocomplete + if (result.matches.length === 1 && result.matches[0].text === result.word) return null + return result.matches.length + ? { + list: result.matches, + from: codeMirrorLib.Pos(cursor.line, result.startCharIndex), + to: codeMirrorLib.Pos(cursor.line, result.endCharIndex) + } + : null + } + register() { + const codeMirrorLib = this._getCodeMirrorLib() + codeMirrorLib.defineMode(this._name, () => this) + codeMirrorLib.defineMIME("text/" + this._name, this._name) + return this + } + _advanceStreamAndReturnTokenType(stream, state) { + let nextCharacter = stream.next() + const lineNumber = stream.lineOracle.line + 1 // state.lineIndex + const WordBreakSymbol = " " + const ParticleBreakSymbol = "\n" + while (typeof nextCharacter === "string") { + const peek = stream.peek() + if (nextCharacter === WordBreakSymbol) { + if (peek === undefined || peek === ParticleBreakSymbol) { + stream.skipToEnd() // advance string to end + this._incrementLine(state) + } + if (peek === WordBreakSymbol && state.cellIndex) { + // If we are missing a cell. + // TODO: this is broken for a blank 1st cell. We need to track WordBreakSymbol level. + state.cellIndex++ + } + return "bracket" + } + if (peek === WordBreakSymbol) { + state.cellIndex++ + return this._getCellStyle(lineNumber, state.cellIndex) + } + nextCharacter = stream.next() + } + state.cellIndex++ + const style = this._getCellStyle(lineNumber, state.cellIndex) + this._incrementLine(state) + return style + } + _getCellStyle(lineIndex, cellIndex) { + const program = this._getParsedProgram() + // todo: if the current word is an error, don't show red? + if (!program.getCellPaintAtPosition) console.log(program) + const paint = program.getCellPaintAtPosition(lineIndex, cellIndex) + const style = paint ? textMateScopeToCodeMirrorStyle(paint.split(".")) : undefined + return style || "noPaintDefinedInParsers" + } + // todo: remove. + startState() { + return { + cellIndex: 0 + } + } + _incrementLine(state) { + state.cellIndex = 0 + } +} +window.ParsersCodeMirrorMode = ParsersCodeMirrorMode + + +{ + class stumpParser extends ParserBackedParticle { + createParserCombinator() { + return new Particle.ParserCombinator( + errorParser, + Object.assign(Object.assign({}, super.createParserCombinator()._getFirstWordMapAsObject()), { + blockquote: htmlTagParser, + colgroup: htmlTagParser, + datalist: htmlTagParser, + fieldset: htmlTagParser, + menuitem: htmlTagParser, + noscript: htmlTagParser, + optgroup: htmlTagParser, + progress: htmlTagParser, + styleTag: htmlTagParser, + template: htmlTagParser, + textarea: htmlTagParser, + titleTag: htmlTagParser, + address: htmlTagParser, + article: htmlTagParser, + caption: htmlTagParser, + details: htmlTagParser, + section: htmlTagParser, + summary: htmlTagParser, + button: htmlTagParser, + canvas: htmlTagParser, + dialog: htmlTagParser, + figure: htmlTagParser, + footer: htmlTagParser, + header: htmlTagParser, + hgroup: htmlTagParser, + iframe: htmlTagParser, + keygen: htmlTagParser, + legend: htmlTagParser, + object: htmlTagParser, + option: htmlTagParser, + output: htmlTagParser, + script: htmlTagParser, + select: htmlTagParser, + source: htmlTagParser, + strong: htmlTagParser, + aside: htmlTagParser, + embed: htmlTagParser, + input: htmlTagParser, + label: htmlTagParser, + meter: htmlTagParser, + param: htmlTagParser, + small: htmlTagParser, + table: htmlTagParser, + tbody: htmlTagParser, + tfoot: htmlTagParser, + thead: htmlTagParser, + track: htmlTagParser, + video: htmlTagParser, + abbr: htmlTagParser, + area: htmlTagParser, + base: htmlTagParser, + body: htmlTagParser, + code: htmlTagParser, + form: htmlTagParser, + head: htmlTagParser, + html: htmlTagParser, + link: htmlTagParser, + main: htmlTagParser, + mark: htmlTagParser, + menu: htmlTagParser, + meta: htmlTagParser, + ruby: htmlTagParser, + samp: htmlTagParser, + span: htmlTagParser, + time: htmlTagParser, + bdi: htmlTagParser, + bdo: htmlTagParser, + col: htmlTagParser, + del: htmlTagParser, + dfn: htmlTagParser, + div: htmlTagParser, + img: htmlTagParser, + ins: htmlTagParser, + kbd: htmlTagParser, + map: htmlTagParser, + nav: htmlTagParser, + pre: htmlTagParser, + rtc: htmlTagParser, + sub: htmlTagParser, + sup: htmlTagParser, + var: htmlTagParser, + wbr: htmlTagParser, + br: htmlTagParser, + dd: htmlTagParser, + dl: htmlTagParser, + dt: htmlTagParser, + em: htmlTagParser, + h1: htmlTagParser, + h2: htmlTagParser, + h3: htmlTagParser, + h4: htmlTagParser, + h5: htmlTagParser, + h6: htmlTagParser, + hr: htmlTagParser, + li: htmlTagParser, + ol: htmlTagParser, + rb: htmlTagParser, + rp: htmlTagParser, + rt: htmlTagParser, + td: htmlTagParser, + th: htmlTagParser, + tr: htmlTagParser, + ul: htmlTagParser, + a: htmlTagParser, + b: htmlTagParser, + i: htmlTagParser, + p: htmlTagParser, + q: htmlTagParser, + s: htmlTagParser, + u: htmlTagParser + }), + [ + { regex: /^$/, parser: blankLineParser }, + { regex: /^[a-zA-Z0-9_]+Component/, parser: componentDefinitionParser } + ] + ) + } + compile() { + return this.asHtml + } + _getHtmlJoinByCharacter() { + return "" + } + static cachedHandParsersProgramRoot = new HandParsersProgram(`// Cell parsers +anyCell +keywordCell +emptyCell +extraCell + paint invalid +anyHtmlContentCell + paint string +attributeValueCell + paint constant.language +componentTagNameCell + paint variable.function + extends keywordCell +htmlTagNameCell + paint variable.function + extends keywordCell + enum a abbr address area article aside b base bdi bdo blockquote body br button canvas caption code col colgroup datalist dd del details dfn dialog div dl dt em embed fieldset figure footer form h1 h2 h3 h4 h5 h6 head header hgroup hr html i iframe img input ins kbd keygen label legend li link main map mark menu menuitem meta meter nav noscript object ol optgroup option output p param pre progress q rb rp rt rtc ruby s samp script section select small source span strong styleTag sub summary sup table tbody td template textarea tfoot th thead time titleTag tr track u ul var video wbr +htmlAttributeNameCell + paint entity.name.type + extends keywordCell + enum accept accept-charset accesskey action align alt async autocomplete autofocus autoplay bgcolor border charset checked class color cols colspan content contenteditable controls coords datetime default defer dir dirname disabled download draggable dropzone enctype for formaction headers height hidden high href hreflang http-equiv id ismap kind lang list loop low max maxlength media method min multiple muted name novalidate onabort onafterprint onbeforeprint onbeforeunload onblur oncanplay oncanplaythrough onchange onclick oncontextmenu oncopy oncuechange oncut ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended onerror onfocus onhashchange oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart onmousedown onmousemove onmouseout onmouseover onmouseup onmousewheel onoffline ononline onpagehide onpageshow onpaste onpause onplay onplaying onpopstate onprogress onratechange onreset onresize onscroll onsearch onseeked onseeking onselect onstalled onstorage onsubmit onsuspend ontimeupdate ontoggle onunload onvolumechange onwaiting onwheel open optimum pattern placeholder poster preload property readonly rel required reversed rows rowspan sandbox scope selected shape size sizes spellcheck src srcdoc srclang srcset start step style tabindex target title translate type usemap value width wrap +bernKeywordCell + enum bern + extends keywordCell + +// Line parsers +stumpParser + root + description A prefix Language that compiles to HTML. + catchAllParser errorParser + inScope htmlTagParser blankLineParser + example + div + h1 hello world + compilesTo html + javascript + compile() { + return this.asHtml + } + _getHtmlJoinByCharacter() { + return "" + } +blankLineParser + pattern ^$ + tags doNotSynthesize + cells emptyCell + javascript + _toHtml() { + return "" + } + getTextContent() {return ""} +htmlTagParser + inScope bernParser htmlTagParser htmlAttributeParser blankLineParser + catchAllCellType anyHtmlContentCell + cells htmlTagNameCell + javascript + isHtmlTagParser = true + getTag() { + // we need to remove the "Tag" bit to handle the style and title attribute/tag conflict. + const firstWord = this.firstWord + const map = { + titleTag: "title", + styleTag: "style" + } + return map[firstWord] || firstWord + } + _getHtmlJoinByCharacter() { + return "" + } + asHtmlWithSuids() { + return this._toHtml(undefined, true) + } + _getOneLiner() { + const oneLinerWords = this.getWordsFrom(1) + return oneLinerWords.length ? oneLinerWords.join(" ") : "" + } + getTextContent() { + return this._getOneLiner() + } + shouldCollapse() { + return this.has("collapse") + } + get domElement() { + var elem = document.createElement(this.getTag()) + elem.setAttribute("stumpUid", this._getUid()) + this.filter(particle => particle.isAttributeParser) + .forEach(child => elem.setAttribute(child.firstWord, child.content)) + elem.innerHTML = this.has("bern") ? this.getParticle("bern").childrenToString() : this._getOneLiner() + this.filter(particle => particle.isHtmlTagParser) + .forEach(child => elem.appendChild(child.domElement)) + return elem + } + _toHtml(indentCount, withSuid) { + const tag = this.getTag() + const children = this.map(child => child._toHtml(indentCount + 1, withSuid)).join("") + const attributesStr = this.filter(particle => particle.isAttributeParser) + .map(child => child.getAttribute()) + .join("") + const indent = " ".repeat(indentCount) + const collapse = this.shouldCollapse() + const indentForChildParsers = !collapse && this.getChildInstancesOfParserId("htmlTagParser").length > 0 + const suid = withSuid ? \` stumpUid="\${this._getUid()}"\` : "" + const oneLiner = this._getOneLiner() + return \`\${!collapse ? indent : ""}<\${tag}\${attributesStr}\${suid}>\${oneLiner}\${indentForChildParsers ? "\\n" : ""}\${children}\${collapse ? "" : "\\n"}\` + } + removeCssStumpParticle() { + return this.removeStumpParticle() + } + removeStumpParticle() { + this.getShadow().removeShadow() + return this.destroy() + } + getParticleByGuid(guid) { + return this.topDownArray.find(particle => particle._getUid() === guid) + } + addClassToStumpParticle(className) { + const classParser = this.touchParticle("class") + const words = classParser.getWordsFrom(1) + // note: we call add on shadow regardless, because at the moment stump may have gotten out of + // sync with shadow, if things modified the dom. todo: cleanup. + this.getShadow().addClassToShadow(className) + if (words.includes(className)) return this + words.push(className) + classParser.setContent(words.join(this.wordBreakSymbol)) + return this + } + removeClassFromStumpParticle(className) { + const classParser = this.getParticle("class") + if (!classParser) return this + const newClasses = classParser.words.filter(word => word !== className) + if (!newClasses.length) classParser.destroy() + else classParser.setContent(newClasses.join(" ")) + this.getShadow().removeClassFromShadow(className) + return this + } + stumpParticleHasClass(className) { + const classParser = this.getParticle("class") + return classParser && classParser.words.includes(className) ? true : false + } + isStumpParticleCheckbox() { + return this.get("type") === "checkbox" + } + getShadow() { + if (!this._shadow) { + const shadowClass = this.getShadowClass() + this._shadow = new shadowClass(this) + } + return this._shadow + } + insertCssChildParticle(text, index) { + return this.insertChildParticle(text, index) + } + insertChildParticle(text, index) { + const singleParticle = new Particle(text).getChildren()[0] + const newParticle = this.insertLineAndChildren(singleParticle.getLine(), singleParticle.childrenToString(), index) + const stumpParserIndex = this.filter(particle => particle.isHtmlTagParser).indexOf(newParticle) + this.getShadow().insertHtmlParticle(newParticle, stumpParserIndex) + return newParticle + } + isInputType() { + return ["input", "textarea"].includes(this.getTag()) || this.get("contenteditable") === "true" + } + findStumpParticleByChild(line) { + return this.findStumpParticlesByChild(line)[0] + } + findStumpParticleByChildString(line) { + return this.topDownArray.find(particle => + particle + .map(child => child.getLine()) + .join("\\n") + .includes(line) + ) + } + findStumpParticleByFirstWord(firstWord) { + return this._findStumpParticlesByBase(firstWord)[0] + } + _findStumpParticlesByBase(firstWord) { + return this.topDownArray.filter(particle => particle.doesExtend("htmlTagParser") && particle.firstWord === firstWord) + } + hasLine(line) { + return this.getChildren().some(particle => particle.getLine() === line) + } + findStumpParticlesByChild(line) { + return this.topDownArray.filter(particle => particle.doesExtend("htmlTagParser") && particle.hasLine(line)) + } + findStumpParticlesWithClass(className) { + return this.topDownArray.filter( + particle => + particle.doesExtend("htmlTagParser") && + particle.has("class") && + particle + .getParticle("class") + .words + .includes(className) + ) + } + getShadowClass() { + return this.parent.getShadowClass() + } + // todo: should not be here + getStumpParticleParticleComponent() { + return this._particleComponent || this.parent.getStumpParticleParticleComponent() + } + // todo: should not be here + setStumpParticleParticleComponent(particleComponent) { + this._particleComponent = particleComponent + return this + } + getStumpParticleCss(prop) { + return this.getShadow().getShadowCss(prop) + } + getStumpParticleAttr(key) { + return this.get(key) + } + setStumpParticleAttr(key, value) { + // todo + return this + } + get asHtml() { + return this._toHtml() + } +errorParser + baseParser errorParser +componentDefinitionParser + extends htmlTagParser + pattern ^[a-zA-Z0-9_]+Component + cells componentTagNameCell + javascript + getTag() { + return "div" + } +htmlAttributeParser + javascript + _toHtml() { + return "" + } + getTextContent() {return ""} + getAttribute() { + return \` \${this.firstWord}="\${this.content}"\` + } + boolean isAttributeParser true + boolean isTileAttribute true + catchAllParser errorParser + catchAllCellType attributeValueCell + cells htmlAttributeNameCell +stumpExtendedAttributeNameCell + extends htmlAttributeNameCell + enum collapse blurCommand changeCommand clickCommand contextMenuCommand doubleClickCommand keyUpCommand lineClickCommand lineShiftClickCommand shiftClickCommand +stumpExtendedAttributeParser + description Parser types not present in HTML but included in stump. + extends htmlAttributeParser + cells stumpExtendedAttributeNameCell +lineOfHtmlContentParser + boolean isTileAttribute true + catchAllParser lineOfHtmlContentParser + catchAllCellType anyHtmlContentCell + javascript + getTextContent() {return this.getLine()} +bernParser + boolean isTileAttribute true + // todo Rename this particle type + description This is a particle where you can put any HTML content. It is called "bern" until someone comes up with a better name. + catchAllParser lineOfHtmlContentParser + javascript + _toHtml() { + return this.childrenToString() + } + getTextContent() {return ""} + cells bernKeywordCell`) + get handParsersProgram() { + return this.constructor.cachedHandParsersProgramRoot + } + static rootParser = stumpParser + } + + class blankLineParser extends ParserBackedParticle { + get emptyCell() { + return this.getWord(0) + } + _toHtml() { + return "" + } + getTextContent() { + return "" + } + } + + class htmlTagParser extends ParserBackedParticle { + createParserCombinator() { + return new Particle.ParserCombinator( + undefined, + Object.assign(Object.assign({}, super.createParserCombinator()._getFirstWordMapAsObject()), { + blockquote: htmlTagParser, + colgroup: htmlTagParser, + datalist: htmlTagParser, + fieldset: htmlTagParser, + menuitem: htmlTagParser, + noscript: htmlTagParser, + optgroup: htmlTagParser, + progress: htmlTagParser, + styleTag: htmlTagParser, + template: htmlTagParser, + textarea: htmlTagParser, + titleTag: htmlTagParser, + address: htmlTagParser, + article: htmlTagParser, + caption: htmlTagParser, + details: htmlTagParser, + section: htmlTagParser, + summary: htmlTagParser, + button: htmlTagParser, + canvas: htmlTagParser, + dialog: htmlTagParser, + figure: htmlTagParser, + footer: htmlTagParser, + header: htmlTagParser, + hgroup: htmlTagParser, + iframe: htmlTagParser, + keygen: htmlTagParser, + legend: htmlTagParser, + object: htmlTagParser, + option: htmlTagParser, + output: htmlTagParser, + script: htmlTagParser, + select: htmlTagParser, + source: htmlTagParser, + strong: htmlTagParser, + aside: htmlTagParser, + embed: htmlTagParser, + input: htmlTagParser, + label: htmlTagParser, + meter: htmlTagParser, + param: htmlTagParser, + small: htmlTagParser, + table: htmlTagParser, + tbody: htmlTagParser, + tfoot: htmlTagParser, + thead: htmlTagParser, + track: htmlTagParser, + video: htmlTagParser, + abbr: htmlTagParser, + area: htmlTagParser, + base: htmlTagParser, + body: htmlTagParser, + code: htmlTagParser, + form: htmlTagParser, + head: htmlTagParser, + html: htmlTagParser, + link: htmlTagParser, + main: htmlTagParser, + mark: htmlTagParser, + menu: htmlTagParser, + meta: htmlTagParser, + ruby: htmlTagParser, + samp: htmlTagParser, + span: htmlTagParser, + time: htmlTagParser, + bdi: htmlTagParser, + bdo: htmlTagParser, + col: htmlTagParser, + del: htmlTagParser, + dfn: htmlTagParser, + div: htmlTagParser, + img: htmlTagParser, + ins: htmlTagParser, + kbd: htmlTagParser, + map: htmlTagParser, + nav: htmlTagParser, + pre: htmlTagParser, + rtc: htmlTagParser, + sub: htmlTagParser, + sup: htmlTagParser, + var: htmlTagParser, + wbr: htmlTagParser, + br: htmlTagParser, + dd: htmlTagParser, + dl: htmlTagParser, + dt: htmlTagParser, + em: htmlTagParser, + h1: htmlTagParser, + h2: htmlTagParser, + h3: htmlTagParser, + h4: htmlTagParser, + h5: htmlTagParser, + h6: htmlTagParser, + hr: htmlTagParser, + li: htmlTagParser, + ol: htmlTagParser, + rb: htmlTagParser, + rp: htmlTagParser, + rt: htmlTagParser, + td: htmlTagParser, + th: htmlTagParser, + tr: htmlTagParser, + ul: htmlTagParser, + a: htmlTagParser, + b: htmlTagParser, + i: htmlTagParser, + p: htmlTagParser, + q: htmlTagParser, + s: htmlTagParser, + u: htmlTagParser, + oncanplaythrough: htmlAttributeParser, + ondurationchange: htmlAttributeParser, + onloadedmetadata: htmlAttributeParser, + contenteditable: htmlAttributeParser, + "accept-charset": htmlAttributeParser, + onbeforeunload: htmlAttributeParser, + onvolumechange: htmlAttributeParser, + onbeforeprint: htmlAttributeParser, + oncontextmenu: htmlAttributeParser, + autocomplete: htmlAttributeParser, + onafterprint: htmlAttributeParser, + onhashchange: htmlAttributeParser, + onloadeddata: htmlAttributeParser, + onmousewheel: htmlAttributeParser, + onratechange: htmlAttributeParser, + ontimeupdate: htmlAttributeParser, + oncuechange: htmlAttributeParser, + ondragenter: htmlAttributeParser, + ondragleave: htmlAttributeParser, + ondragstart: htmlAttributeParser, + onloadstart: htmlAttributeParser, + onmousedown: htmlAttributeParser, + onmousemove: htmlAttributeParser, + onmouseover: htmlAttributeParser, + placeholder: htmlAttributeParser, + formaction: htmlAttributeParser, + "http-equiv": htmlAttributeParser, + novalidate: htmlAttributeParser, + ondblclick: htmlAttributeParser, + ondragover: htmlAttributeParser, + onkeypress: htmlAttributeParser, + onmouseout: htmlAttributeParser, + onpagehide: htmlAttributeParser, + onpageshow: htmlAttributeParser, + onpopstate: htmlAttributeParser, + onprogress: htmlAttributeParser, + spellcheck: htmlAttributeParser, + accesskey: htmlAttributeParser, + autofocus: htmlAttributeParser, + draggable: htmlAttributeParser, + maxlength: htmlAttributeParser, + oncanplay: htmlAttributeParser, + ondragend: htmlAttributeParser, + onemptied: htmlAttributeParser, + oninvalid: htmlAttributeParser, + onkeydown: htmlAttributeParser, + onmouseup: htmlAttributeParser, + onoffline: htmlAttributeParser, + onplaying: htmlAttributeParser, + onseeking: htmlAttributeParser, + onstalled: htmlAttributeParser, + onstorage: htmlAttributeParser, + onsuspend: htmlAttributeParser, + onwaiting: htmlAttributeParser, + translate: htmlAttributeParser, + autoplay: htmlAttributeParser, + controls: htmlAttributeParser, + datetime: htmlAttributeParser, + disabled: htmlAttributeParser, + download: htmlAttributeParser, + dropzone: htmlAttributeParser, + hreflang: htmlAttributeParser, + multiple: htmlAttributeParser, + onchange: htmlAttributeParser, + ononline: htmlAttributeParser, + onresize: htmlAttributeParser, + onscroll: htmlAttributeParser, + onsearch: htmlAttributeParser, + onseeked: htmlAttributeParser, + onselect: htmlAttributeParser, + onsubmit: htmlAttributeParser, + ontoggle: htmlAttributeParser, + onunload: htmlAttributeParser, + property: htmlAttributeParser, + readonly: htmlAttributeParser, + required: htmlAttributeParser, + reversed: htmlAttributeParser, + selected: htmlAttributeParser, + tabindex: htmlAttributeParser, + bgcolor: htmlAttributeParser, + charset: htmlAttributeParser, + checked: htmlAttributeParser, + colspan: htmlAttributeParser, + content: htmlAttributeParser, + default: htmlAttributeParser, + dirname: htmlAttributeParser, + enctype: htmlAttributeParser, + headers: htmlAttributeParser, + onabort: htmlAttributeParser, + onclick: htmlAttributeParser, + onended: htmlAttributeParser, + onerror: htmlAttributeParser, + onfocus: htmlAttributeParser, + oninput: htmlAttributeParser, + onkeyup: htmlAttributeParser, + onpaste: htmlAttributeParser, + onpause: htmlAttributeParser, + onreset: htmlAttributeParser, + onwheel: htmlAttributeParser, + optimum: htmlAttributeParser, + pattern: htmlAttributeParser, + preload: htmlAttributeParser, + rowspan: htmlAttributeParser, + sandbox: htmlAttributeParser, + srclang: htmlAttributeParser, + accept: htmlAttributeParser, + action: htmlAttributeParser, + border: htmlAttributeParser, + coords: htmlAttributeParser, + height: htmlAttributeParser, + hidden: htmlAttributeParser, + method: htmlAttributeParser, + onblur: htmlAttributeParser, + oncopy: htmlAttributeParser, + ondrag: htmlAttributeParser, + ondrop: htmlAttributeParser, + onload: htmlAttributeParser, + onplay: htmlAttributeParser, + poster: htmlAttributeParser, + srcdoc: htmlAttributeParser, + srcset: htmlAttributeParser, + target: htmlAttributeParser, + usemap: htmlAttributeParser, + align: htmlAttributeParser, + async: htmlAttributeParser, + class: htmlAttributeParser, + color: htmlAttributeParser, + defer: htmlAttributeParser, + ismap: htmlAttributeParser, + media: htmlAttributeParser, + muted: htmlAttributeParser, + oncut: htmlAttributeParser, + scope: htmlAttributeParser, + shape: htmlAttributeParser, + sizes: htmlAttributeParser, + start: htmlAttributeParser, + style: htmlAttributeParser, + title: htmlAttributeParser, + value: htmlAttributeParser, + width: htmlAttributeParser, + cols: htmlAttributeParser, + high: htmlAttributeParser, + href: htmlAttributeParser, + kind: htmlAttributeParser, + lang: htmlAttributeParser, + list: htmlAttributeParser, + loop: htmlAttributeParser, + name: htmlAttributeParser, + open: htmlAttributeParser, + rows: htmlAttributeParser, + size: htmlAttributeParser, + step: htmlAttributeParser, + type: htmlAttributeParser, + wrap: htmlAttributeParser, + alt: htmlAttributeParser, + dir: htmlAttributeParser, + for: htmlAttributeParser, + low: htmlAttributeParser, + max: htmlAttributeParser, + min: htmlAttributeParser, + rel: htmlAttributeParser, + src: htmlAttributeParser, + id: htmlAttributeParser, + lineShiftClickCommand: stumpExtendedAttributeParser, + contextMenuCommand: stumpExtendedAttributeParser, + doubleClickCommand: stumpExtendedAttributeParser, + shiftClickCommand: stumpExtendedAttributeParser, + lineClickCommand: stumpExtendedAttributeParser, + changeCommand: stumpExtendedAttributeParser, + clickCommand: stumpExtendedAttributeParser, + keyUpCommand: stumpExtendedAttributeParser, + blurCommand: stumpExtendedAttributeParser, + collapse: stumpExtendedAttributeParser, + bern: bernParser + }), + [ + { regex: /^$/, parser: blankLineParser }, + { regex: /^[a-zA-Z0-9_]+Component/, parser: componentDefinitionParser } + ] + ) + } + get htmlTagNameCell() { + return this.getWord(0) + } + get anyHtmlContentCell() { + return this.getWordsFrom(1) + } + isHtmlTagParser = true + getTag() { + // we need to remove the "Tag" bit to handle the style and title attribute/tag conflict. + const firstWord = this.firstWord + const map = { + titleTag: "title", + styleTag: "style" + } + return map[firstWord] || firstWord + } + _getHtmlJoinByCharacter() { + return "" + } + asHtmlWithSuids() { + return this._toHtml(undefined, true) + } + _getOneLiner() { + const oneLinerWords = this.getWordsFrom(1) + return oneLinerWords.length ? oneLinerWords.join(" ") : "" + } + getTextContent() { + return this._getOneLiner() + } + shouldCollapse() { + return this.has("collapse") + } + get domElement() { + var elem = document.createElement(this.getTag()) + elem.setAttribute("stumpUid", this._getUid()) + this.filter(particle => particle.isAttributeParser).forEach(child => elem.setAttribute(child.firstWord, child.content)) + elem.innerHTML = this.has("bern") ? this.getParticle("bern").childrenToString() : this._getOneLiner() + this.filter(particle => particle.isHtmlTagParser).forEach(child => elem.appendChild(child.domElement)) + return elem + } + _toHtml(indentCount, withSuid) { + const tag = this.getTag() + const children = this.map(child => child._toHtml(indentCount + 1, withSuid)).join("") + const attributesStr = this.filter(particle => particle.isAttributeParser) + .map(child => child.getAttribute()) + .join("") + const indent = " ".repeat(indentCount) + const collapse = this.shouldCollapse() + const indentForChildParsers = !collapse && this.getChildInstancesOfParserId("htmlTagParser").length > 0 + const suid = withSuid ? ` stumpUid="${this._getUid()}"` : "" + const oneLiner = this._getOneLiner() + return `${!collapse ? indent : ""}<${tag}${attributesStr}${suid}>${oneLiner}${indentForChildParsers ? "\n" : ""}${children}${collapse ? "" : "\n"}` + } + removeCssStumpParticle() { + return this.removeStumpParticle() + } + removeStumpParticle() { + this.getShadow().removeShadow() + return this.destroy() + } + getParticleByGuid(guid) { + return this.topDownArray.find(particle => particle._getUid() === guid) + } + addClassToStumpParticle(className) { + const classParser = this.touchParticle("class") + const words = classParser.getWordsFrom(1) + // note: we call add on shadow regardless, because at the moment stump may have gotten out of + // sync with shadow, if things modified the dom. todo: cleanup. + this.getShadow().addClassToShadow(className) + if (words.includes(className)) return this + words.push(className) + classParser.setContent(words.join(this.wordBreakSymbol)) + return this + } + removeClassFromStumpParticle(className) { + const classParser = this.getParticle("class") + if (!classParser) return this + const newClasses = classParser.words.filter(word => word !== className) + if (!newClasses.length) classParser.destroy() + else classParser.setContent(newClasses.join(" ")) + this.getShadow().removeClassFromShadow(className) + return this + } + stumpParticleHasClass(className) { + const classParser = this.getParticle("class") + return classParser && classParser.words.includes(className) ? true : false + } + isStumpParticleCheckbox() { + return this.get("type") === "checkbox" + } + getShadow() { + if (!this._shadow) { + const shadowClass = this.getShadowClass() + this._shadow = new shadowClass(this) + } + return this._shadow + } + insertCssChildParticle(text, index) { + return this.insertChildParticle(text, index) + } + insertChildParticle(text, index) { + const singleParticle = new Particle(text).getChildren()[0] + const newParticle = this.insertLineAndChildren(singleParticle.getLine(), singleParticle.childrenToString(), index) + const stumpParserIndex = this.filter(particle => particle.isHtmlTagParser).indexOf(newParticle) + this.getShadow().insertHtmlParticle(newParticle, stumpParserIndex) + return newParticle + } + isInputType() { + return ["input", "textarea"].includes(this.getTag()) || this.get("contenteditable") === "true" + } + findStumpParticleByChild(line) { + return this.findStumpParticlesByChild(line)[0] + } + findStumpParticleByChildString(line) { + return this.topDownArray.find(particle => + particle + .map(child => child.getLine()) + .join("\n") + .includes(line) + ) + } + findStumpParticleByFirstWord(firstWord) { + return this._findStumpParticlesByBase(firstWord)[0] + } + _findStumpParticlesByBase(firstWord) { + return this.topDownArray.filter(particle => particle.doesExtend("htmlTagParser") && particle.firstWord === firstWord) + } + hasLine(line) { + return this.getChildren().some(particle => particle.getLine() === line) + } + findStumpParticlesByChild(line) { + return this.topDownArray.filter(particle => particle.doesExtend("htmlTagParser") && particle.hasLine(line)) + } + findStumpParticlesWithClass(className) { + return this.topDownArray.filter(particle => particle.doesExtend("htmlTagParser") && particle.has("class") && particle.getParticle("class").words.includes(className)) + } + getShadowClass() { + return this.parent.getShadowClass() + } + // todo: should not be here + getStumpParticleParticleComponent() { + return this._particleComponent || this.parent.getStumpParticleParticleComponent() + } + // todo: should not be here + setStumpParticleParticleComponent(particleComponent) { + this._particleComponent = particleComponent + return this + } + getStumpParticleCss(prop) { + return this.getShadow().getShadowCss(prop) + } + getStumpParticleAttr(key) { + return this.get(key) + } + setStumpParticleAttr(key, value) { + // todo + return this + } + get asHtml() { + return this._toHtml() + } + } + + class errorParser extends ParserBackedParticle { + getErrors() { + return this._getErrorParserErrors() + } + } + + class componentDefinitionParser extends htmlTagParser { + get componentTagNameCell() { + return this.getWord(0) + } + getTag() { + return "div" + } + } + + class htmlAttributeParser extends ParserBackedParticle { + createParserCombinator() { + return new Particle.ParserCombinator(errorParser, undefined, undefined) + } + get htmlAttributeNameCell() { + return this.getWord(0) + } + get attributeValueCell() { + return this.getWordsFrom(1) + } + get isTileAttribute() { + return true + } + get isAttributeParser() { + return true + } + _toHtml() { + return "" + } + getTextContent() { + return "" + } + getAttribute() { + return ` ${this.firstWord}="${this.content}"` + } + } + + class stumpExtendedAttributeParser extends htmlAttributeParser { + get stumpExtendedAttributeNameCell() { + return this.getWord(0) + } + } + + class lineOfHtmlContentParser extends ParserBackedParticle { + createParserCombinator() { + return new Particle.ParserCombinator(lineOfHtmlContentParser, undefined, undefined) + } + get anyHtmlContentCell() { + return this.getWordsFrom(0) + } + get isTileAttribute() { + return true + } + getTextContent() { + return this.getLine() + } + } + + class bernParser extends ParserBackedParticle { + createParserCombinator() { + return new Particle.ParserCombinator(lineOfHtmlContentParser, undefined, undefined) + } + get bernKeywordCell() { + return this.getWord(0) + } + get isTileAttribute() { + return true + } + _toHtml() { + return this.childrenToString() + } + getTextContent() { + return "" + } + } + + window.stumpParser = stumpParser +} + + +{ + class hakonParser extends ParserBackedParticle { + createParserCombinator() { + return new Particle.ParserCombinator(selectorParser, Object.assign(Object.assign({}, super.createParserCombinator()._getFirstWordMapAsObject()), { comment: commentParser }), undefined) + } + getSelector() { + return "" + } + compile() { + return this.topDownArray + .filter(particle => particle.isSelectorParser) + .map(child => child.compile()) + .join("") + } + static cachedHandParsersProgramRoot = new HandParsersProgram(`// Cell Parsers +anyCell +keywordCell +commentKeywordCell + extends keywordCell + paint comment + enum comment +extraCell + paint invalid +cssValueCell + paint constant.numeric +selectorCell + paint keyword.control + examples body h1 + // todo add html tags, css and ids selector regexes, etc +vendorPrefixPropertyKeywordCell + description Properties like -moz-column-fill + paint variable.function + extends keywordCell +propertyKeywordCell + paint variable.function + extends keywordCell + // todo Where are these coming from? Can we add a url link + enum align-content align-items align-self all animation animation-delay animation-direction animation-duration animation-fill-mode animation-iteration-count animation-name animation-play-state animation-timing-function backface-visibility background background-attachment background-blend-mode background-clip background-color background-image background-origin background-position background-repeat background-size border border-bottom border-bottom-color border-bottom-left-radius border-bottom-right-radius border-bottom-style border-bottom-width border-collapse border-color border-image border-image-outset border-image-repeat border-image-slice border-image-source border-image-width border-left border-left-color border-left-style border-left-width border-radius border-right border-right-color border-right-style border-right-width border-spacing border-style border-top border-top-color border-top-left-radius border-top-right-radius border-top-style border-top-width border-width bottom box-shadow box-sizing break-inside caption-side clear clip color column-count column-fill column-gap column-rule column-rule-color column-rule-style column-rule-width column-span column-width columns content counter-increment counter-reset cursor direction display empty-cells fill filter flex flex-basis flex-direction flex-flow flex-grow flex-shrink flex-wrap float font @font-face font-family font-size font-size-adjust font-stretch font-style font-variant font-weight hanging-punctuation height hyphens justify-content @keyframes left letter-spacing line-height list-style list-style-image list-style-position list-style-type margin margin-bottom margin-left margin-right margin-top max-height max-width @media min-height min-width nav-down nav-index nav-left nav-right nav-up opacity order outline outline-color outline-offset outline-style outline-width overflow overflow-x overflow-y padding padding-bottom padding-left padding-right padding-top page-break-after page-break-before page-break-inside perspective perspective-origin position quotes resize right tab-size table-layout text-align text-align-last text-decoration text-decoration-color text-decoration-line text-decoration-style text-indent text-justify text-overflow text-shadow text-transform top transform transform-origin transform-style transition transition-delay transition-duration transition-property transition-timing-function unicode-bidi vertical-align visibility white-space width word-break word-spacing word-wrap z-index overscroll-behavior-x user-select -ms-touch-action -webkit-user-select -webkit-touch-callout -moz-user-select touch-action -ms-user-select -khtml-user-select gap grid-auto-flow grid-column grid-column-end grid-column-gap grid-column-start grid-gap grid-row grid-row-end grid-row-gap grid-row-start grid-template-columns grid-template-rows justify-items justify-self +errorCell + paint invalid +commentCell + paint comment + +// Line Parsers +hakonParser + root + // todo Add variables? + description A prefix Language that compiles to CSS + compilesTo css + inScope commentParser + catchAllParser selectorParser + javascript + getSelector() { + return "" + } + compile() { + return this.topDownArray + .filter(particle => particle.isSelectorParser) + .map(child => child.compile()) + .join("") + } + example A basic example + body + font-size 12px + h1,h2 + color red + a + &:hover + color blue + font-size 17px +propertyParser + catchAllCellType cssValueCell + catchAllParser errorParser + javascript + compile(spaces) { + return \`\${spaces}\${this.firstWord}: \${this.content};\` + } + cells propertyKeywordCell +variableParser + extends propertyParser + pattern -- +browserPrefixPropertyParser + extends propertyParser + pattern ^\\-\\w.+ + cells vendorPrefixPropertyKeywordCell +errorParser + catchAllParser errorParser + catchAllCellType errorCell + baseParser errorParser +commentParser + cells commentKeywordCell + catchAllCellType commentCell + catchAllParser commentParser +selectorParser + inScope propertyParser variableParser commentParser + catchAllParser selectorParser + boolean isSelectorParser true + javascript + getSelector() { + const parentSelector = this.parent.getSelector() + return this.firstWord + .split(",") + .map(part => { + if (part.startsWith("&")) return parentSelector + part.substr(1) + return parentSelector ? parentSelector + " " + part : part + }) + .join(",") + } + compile() { + const propertyParsers = this.getChildren().filter(particle => particle.doesExtend("propertyParser")) + if (!propertyParsers.length) return "" + const spaces = " " + return \`\${this.getSelector()} { + \${propertyParsers.map(child => child.compile(spaces)).join("\\n")} + }\\n\` + } + cells selectorCell`) + get handParsersProgram() { + return this.constructor.cachedHandParsersProgramRoot + } + static rootParser = hakonParser + } + + class propertyParser extends ParserBackedParticle { + createParserCombinator() { + return new Particle.ParserCombinator(errorParser, undefined, undefined) + } + get propertyKeywordCell() { + return this.getWord(0) + } + get cssValueCell() { + return this.getWordsFrom(1) + } + compile(spaces) { + return `${spaces}${this.firstWord}: ${this.content};` + } + } + + class variableParser extends propertyParser {} + + class browserPrefixPropertyParser extends propertyParser { + get vendorPrefixPropertyKeywordCell() { + return this.getWord(0) + } + } + + class errorParser extends ParserBackedParticle { + createParserCombinator() { + return new Particle.ParserCombinator(errorParser, undefined, undefined) + } + getErrors() { + return this._getErrorParserErrors() + } + get errorCell() { + return this.getWordsFrom(0) + } + } + + class commentParser extends ParserBackedParticle { + createParserCombinator() { + return new Particle.ParserCombinator(commentParser, undefined, undefined) + } + get commentKeywordCell() { + return this.getWord(0) + } + get commentCell() { + return this.getWordsFrom(1) + } + } + + class selectorParser extends ParserBackedParticle { + createParserCombinator() { + return new Particle.ParserCombinator( + selectorParser, + Object.assign(Object.assign({}, super.createParserCombinator()._getFirstWordMapAsObject()), { + "border-bottom-right-radius": propertyParser, + "transition-timing-function": propertyParser, + "animation-iteration-count": propertyParser, + "animation-timing-function": propertyParser, + "border-bottom-left-radius": propertyParser, + "border-top-right-radius": propertyParser, + "border-top-left-radius": propertyParser, + "background-attachment": propertyParser, + "background-blend-mode": propertyParser, + "text-decoration-color": propertyParser, + "text-decoration-style": propertyParser, + "overscroll-behavior-x": propertyParser, + "-webkit-touch-callout": propertyParser, + "grid-template-columns": propertyParser, + "animation-play-state": propertyParser, + "text-decoration-line": propertyParser, + "animation-direction": propertyParser, + "animation-fill-mode": propertyParser, + "backface-visibility": propertyParser, + "background-position": propertyParser, + "border-bottom-color": propertyParser, + "border-bottom-style": propertyParser, + "border-bottom-width": propertyParser, + "border-image-outset": propertyParser, + "border-image-repeat": propertyParser, + "border-image-source": propertyParser, + "hanging-punctuation": propertyParser, + "list-style-position": propertyParser, + "transition-duration": propertyParser, + "transition-property": propertyParser, + "-webkit-user-select": propertyParser, + "animation-duration": propertyParser, + "border-image-slice": propertyParser, + "border-image-width": propertyParser, + "border-right-color": propertyParser, + "border-right-style": propertyParser, + "border-right-width": propertyParser, + "perspective-origin": propertyParser, + "-khtml-user-select": propertyParser, + "grid-template-rows": propertyParser, + "background-origin": propertyParser, + "background-repeat": propertyParser, + "border-left-color": propertyParser, + "border-left-style": propertyParser, + "border-left-width": propertyParser, + "column-rule-color": propertyParser, + "column-rule-style": propertyParser, + "column-rule-width": propertyParser, + "counter-increment": propertyParser, + "page-break-before": propertyParser, + "page-break-inside": propertyParser, + "grid-column-start": propertyParser, + "background-color": propertyParser, + "background-image": propertyParser, + "border-top-color": propertyParser, + "border-top-style": propertyParser, + "border-top-width": propertyParser, + "font-size-adjust": propertyParser, + "list-style-image": propertyParser, + "page-break-after": propertyParser, + "transform-origin": propertyParser, + "transition-delay": propertyParser, + "-ms-touch-action": propertyParser, + "-moz-user-select": propertyParser, + "animation-delay": propertyParser, + "background-clip": propertyParser, + "background-size": propertyParser, + "border-collapse": propertyParser, + "justify-content": propertyParser, + "list-style-type": propertyParser, + "text-align-last": propertyParser, + "text-decoration": propertyParser, + "transform-style": propertyParser, + "-ms-user-select": propertyParser, + "grid-column-end": propertyParser, + "grid-column-gap": propertyParser, + "animation-name": propertyParser, + "border-spacing": propertyParser, + "flex-direction": propertyParser, + "letter-spacing": propertyParser, + "outline-offset": propertyParser, + "padding-bottom": propertyParser, + "text-transform": propertyParser, + "vertical-align": propertyParser, + "grid-auto-flow": propertyParser, + "grid-row-start": propertyParser, + "align-content": propertyParser, + "border-bottom": propertyParser, + "border-radius": propertyParser, + "counter-reset": propertyParser, + "margin-bottom": propertyParser, + "outline-color": propertyParser, + "outline-style": propertyParser, + "outline-width": propertyParser, + "padding-right": propertyParser, + "text-overflow": propertyParser, + "justify-items": propertyParser, + "border-color": propertyParser, + "border-image": propertyParser, + "border-right": propertyParser, + "border-style": propertyParser, + "border-width": propertyParser, + "break-inside": propertyParser, + "caption-side": propertyParser, + "column-count": propertyParser, + "column-width": propertyParser, + "font-stretch": propertyParser, + "font-variant": propertyParser, + "margin-right": propertyParser, + "padding-left": propertyParser, + "table-layout": propertyParser, + "text-justify": propertyParser, + "unicode-bidi": propertyParser, + "word-spacing": propertyParser, + "touch-action": propertyParser, + "grid-row-end": propertyParser, + "grid-row-gap": propertyParser, + "justify-self": propertyParser, + "align-items": propertyParser, + "border-left": propertyParser, + "column-fill": propertyParser, + "column-rule": propertyParser, + "column-span": propertyParser, + "empty-cells": propertyParser, + "flex-shrink": propertyParser, + "font-family": propertyParser, + "font-weight": propertyParser, + "line-height": propertyParser, + "margin-left": propertyParser, + "padding-top": propertyParser, + perspective: propertyParser, + "text-indent": propertyParser, + "text-shadow": propertyParser, + "white-space": propertyParser, + "user-select": propertyParser, + "grid-column": propertyParser, + "align-self": propertyParser, + background: propertyParser, + "border-top": propertyParser, + "box-shadow": propertyParser, + "box-sizing": propertyParser, + "column-gap": propertyParser, + "flex-basis": propertyParser, + "@font-face": propertyParser, + "font-style": propertyParser, + "@keyframes": propertyParser, + "list-style": propertyParser, + "margin-top": propertyParser, + "max-height": propertyParser, + "min-height": propertyParser, + "overflow-x": propertyParser, + "overflow-y": propertyParser, + "text-align": propertyParser, + transition: propertyParser, + visibility: propertyParser, + "word-break": propertyParser, + animation: propertyParser, + direction: propertyParser, + "flex-flow": propertyParser, + "flex-grow": propertyParser, + "flex-wrap": propertyParser, + "font-size": propertyParser, + "max-width": propertyParser, + "min-width": propertyParser, + "nav-index": propertyParser, + "nav-right": propertyParser, + transform: propertyParser, + "word-wrap": propertyParser, + "nav-down": propertyParser, + "nav-left": propertyParser, + overflow: propertyParser, + position: propertyParser, + "tab-size": propertyParser, + "grid-gap": propertyParser, + "grid-row": propertyParser, + columns: propertyParser, + content: propertyParser, + display: propertyParser, + hyphens: propertyParser, + opacity: propertyParser, + outline: propertyParser, + padding: propertyParser, + "z-index": propertyParser, + border: propertyParser, + bottom: propertyParser, + cursor: propertyParser, + filter: propertyParser, + height: propertyParser, + margin: propertyParser, + "@media": propertyParser, + "nav-up": propertyParser, + quotes: propertyParser, + resize: propertyParser, + clear: propertyParser, + color: propertyParser, + float: propertyParser, + order: propertyParser, + right: propertyParser, + width: propertyParser, + clip: propertyParser, + fill: propertyParser, + flex: propertyParser, + font: propertyParser, + left: propertyParser, + all: propertyParser, + top: propertyParser, + gap: propertyParser, + "": propertyParser, + comment: commentParser + }), + [ + { regex: /--/, parser: variableParser }, + { regex: /^\-\w.+/, parser: browserPrefixPropertyParser } + ] + ) + } + get selectorCell() { + return this.getWord(0) + } + get isSelectorParser() { + return true + } + getSelector() { + const parentSelector = this.parent.getSelector() + return this.firstWord + .split(",") + .map(part => { + if (part.startsWith("&")) return parentSelector + part.substr(1) + return parentSelector ? parentSelector + " " + part : part + }) + .join(",") + } + compile() { + const propertyParsers = this.getChildren().filter(particle => particle.doesExtend("propertyParser")) + if (!propertyParsers.length) return "" + const spaces = " " + return `${this.getSelector()} { +${propertyParsers.map(child => child.compile(spaces)).join("\n")} +}\n` + } + } + + window.hakonParser = hakonParser +} + + +//onsave scrollsdk build produce ParticleComponentFramework.browser.js +const BrowserEvents = {} +BrowserEvents.click = "click" +BrowserEvents.change = "change" +BrowserEvents.mouseover = "mouseover" +BrowserEvents.mouseout = "mouseout" +BrowserEvents.mousedown = "mousedown" +BrowserEvents.contextmenu = "contextmenu" +BrowserEvents.keypress = "keypress" +BrowserEvents.keyup = "keyup" +BrowserEvents.focus = "focus" +BrowserEvents.mousemove = "mousemove" +BrowserEvents.dblclick = "dblclick" +BrowserEvents.submit = "submit" +BrowserEvents.blur = "blur" +BrowserEvents.paste = "paste" +BrowserEvents.copy = "copy" +BrowserEvents.resize = "resize" +BrowserEvents.cut = "cut" +BrowserEvents.drop = "drop" +BrowserEvents.dragover = "dragover" +BrowserEvents.dragenter = "dragenter" +BrowserEvents.dragleave = "dragleave" +BrowserEvents.ready = "ready" +const WillowConstants = {} +// todo: cleanup +WillowConstants.clickCommand = "clickCommand" +WillowConstants.shiftClickCommand = "shiftClickCommand" +WillowConstants.blurCommand = "blurCommand" +WillowConstants.keyUpCommand = "keyUpCommand" +WillowConstants.contextMenuCommand = "contextMenuCommand" +WillowConstants.changeCommand = "changeCommand" +WillowConstants.doubleClickCommand = "doubleClickCommand" +// todo: cleanup +WillowConstants.titleTag = "titleTag" +WillowConstants.styleTag = "styleTag" +WillowConstants.tagMap = {} +WillowConstants.tagMap[WillowConstants.styleTag] = "style" +WillowConstants.tagMap[WillowConstants.titleTag] = "title" +WillowConstants.tags = {} +WillowConstants.tags.html = "html" +WillowConstants.tags.head = "head" +WillowConstants.tags.body = "body" +WillowConstants.collapse = "collapse" +WillowConstants.uidAttribute = "stumpUid" +WillowConstants.class = "class" +WillowConstants.type = "type" +WillowConstants.value = "value" +WillowConstants.name = "name" +WillowConstants.checkbox = "checkbox" +WillowConstants.checkedSelector = ":checked" +WillowConstants.contenteditable = "contenteditable" +WillowConstants.inputTypes = ["input", "textarea"] +var CacheType +;(function (CacheType) { + CacheType["inBrowserMemory"] = "inBrowserMemory" +})(CacheType || (CacheType = {})) +class WillowHTTPResponse { + constructor(superAgentResponse) { + this._cacheType = CacheType.inBrowserMemory + this._fromCache = false + this._cacheTime = Date.now() + this._superAgentResponse = superAgentResponse + this._mimeType = superAgentResponse && superAgentResponse.type + } + // todo: ServerMemoryCacheTime and ServerMemoryDiskCacheTime + get cacheTime() { + return this._cacheTime + } + get cacheType() { + return this._cacheType + } + get body() { + return this._superAgentResponse && this._superAgentResponse.body + } + get text() { + if (this._text === undefined) this._text = this._superAgentResponse && this._superAgentResponse.text ? this._superAgentResponse.text : this.body ? JSON.stringify(this.body, null, 2) : "" + return this._text + } + get asJson() { + return this.body ? this.body : JSON.parse(this.text) + } + get fromCache() { + return this._fromCache + } + setFromCache(val) { + this._fromCache = val + return this + } + getParsedDataOrText() { + if (this._mimeType === "text/csv") return this.text + return this.body || this.text + } +} +class WillowHTTPProxyCacheResponse extends WillowHTTPResponse { + constructor(proxyServerResponse) { + super() + this._proxyServerResponse = proxyServerResponse + this._cacheType = proxyServerResponse.body.cacheType + this._cacheTime = proxyServerResponse.body.cacheTime + this._text = proxyServerResponse.body.text + } +} +class AbstractWillowShadow { + constructor(stumpParticle) { + this._stumpParticle = stumpParticle + } + getShadowStumpParticle() { + return this._stumpParticle + } + getShadowValue() { + return this._val + } + removeShadow() { + return this + } + setInputOrTextAreaValue(value) { + this._val = value + return this + } + getShadowParent() { + return this.getShadowStumpParticle().parent.getShadow() + } + getPositionAndDimensions(gridSize = 1) { + const offset = this.getShadowOffset() + const parentOffset = this.getShadowParent().getShadowOffset() + return { + left: Math.floor((offset.left - parentOffset.left) / gridSize), + top: Math.floor((offset.top - parentOffset.top) / gridSize), + width: Math.floor(this.getShadowWidth() / gridSize), + height: Math.floor(this.getShadowHeight() / gridSize) + } + } + shadowHasClass(name) { + return false + } + getShadowAttr(name) { + return "" + } + makeResizable(options) { + return this + } + makeDraggable(options) { + return this + } + makeSelectable(options) { + return this + } + isShadowChecked() { + return false + } + getShadowOffset() { + return { left: 111, top: 111 } + } + getShadowWidth() { + return 111 + } + getShadowHeight() { + return 111 + } + setShadowAttr(name, value) { + return this + } + isShadowDraggable() { + return this.shadowHasClass("draggable") + } + toggleShadow() {} + addClassToShadow(className) {} + removeClassFromShadow(className) { + return this + } + onShadowEvent(event, fn) { + // todo: + return this + } + onShadowEventWithSelector(event, selector, fn) { + // todo: + return this + } + offShadowEvent(event, fn) { + // todo: + return this + } + triggerShadowEvent(name) { + return this + } + getShadowPosition() { + return { + left: 111, + top: 111 + } + } + getShadowOuterHeight() { + return 11 + } + getShadowOuterWidth() { + return 11 + } + getShadowCss(property) { + return "" + } + insertHtmlParticle(childParticle, index) {} + get element() { + return {} + } +} +class WillowShadow extends AbstractWillowShadow {} +class WillowStore { + constructor() { + this._values = {} + } + get(key) { + return this._values[key] + } + set(key, value) { + this._values[key] = value + return this + } + remove(key) { + delete this._values[key] + } + each(fn) { + Object.keys(this._values).forEach(key => { + fn(this._values[key], key) + }) + } + clearAll() { + this._values = {} + } +} +class WillowMousetrap { + constructor() { + this.prototype = {} + } + bind() {} +} +// this one should have no document, window, $, et cetera. +class AbstractWillowBrowser extends stumpParser { + constructor(fullHtmlPageUrlIncludingProtocolAndFileName) { + super(`${WillowConstants.tags.html} + ${WillowConstants.tags.head} + ${WillowConstants.tags.body}`) + this._offlineMode = false + this._httpGetResponseCache = {} + this.location = {} + this._htmlStumpParticle = this.particleAt(0) + this._headStumpParticle = this.particleAt(0).particleAt(0) + this._bodyStumpParticle = this.particleAt(0).particleAt(1) + this.addSuidsToHtmlHeadAndBodyShadows() + this._fullHtmlPageUrlIncludingProtocolAndFileName = fullHtmlPageUrlIncludingProtocolAndFileName + const url = new URL(fullHtmlPageUrlIncludingProtocolAndFileName) + this.location.port = url.port + this.location.protocol = url.protocol + this.location.hostname = url.hostname + this.location.host = url.host + } + _getPort() { + return this.location.port ? ":" + this.location.port : "" + } + getHash() { + return this.location.hash || "" + } + setHash(value) { + this.location.hash = value + } + setHtmlOfElementWithIdHack(id, html) {} + setHtmlOfElementsWithClassHack(id, html) {} + setValueOfElementWithIdHack(id, value) {} + setValueOfElementWithClassHack(id, value) {} + getElementById(id) {} + queryObjectToQueryString(obj) { + const params = new URLSearchParams() + for (const [key, value] of Object.entries(obj)) { + params.set(key, String(value)) + } + return params.toString() + } + toPrettyDeepLink(particleCode, queryObject) { + // todo: move things to a constant. + const particleBreakSymbol = "~" + const edgeSymbol = "_" + const obj = Object.assign({}, queryObject) + if (!particleCode.includes(particleBreakSymbol) && !particleCode.includes(edgeSymbol)) { + obj.particleBreakSymbol = particleBreakSymbol + obj.edgeSymbol = edgeSymbol + obj.data = encodeURIComponent(particleCode.replace(/ /g, edgeSymbol).replace(/\n/g, particleBreakSymbol)) + } else obj.data = encodeURIComponent(particleCode) + return this.getAppWebPageUrl() + "?" + this.queryObjectToQueryString(obj) + } + getHost() { + return this.location.host + } + reload() {} + toggleOfflineMode() { + this._offlineMode = !this._offlineMode + } + addSuidsToHtmlHeadAndBodyShadows() {} + getShadowClass() { + return WillowShadow + } + getMockMouseEvent() { + return { + clientX: 0, + clientY: 0, + offsetX: 0, + offsetY: 0 + } + } + toggleFullScreen() {} + getMousetrap() { + if (!this._mousetrap) this._mousetrap = new WillowMousetrap() + return this._mousetrap + } + _getFocusedShadow() { + return this._focusedShadow || this.getBodyStumpParticle().getShadow() + } + getHeadStumpParticle() { + return this._headStumpParticle + } + getBodyStumpParticle() { + return this._bodyStumpParticle + } + getHtmlStumpParticle() { + return this._htmlStumpParticle + } + getStore() { + if (!this._store) this._store = new WillowStore() + return this._store + } + someInputHasFocus() { + const focusedShadow = this._getFocusedShadow() + if (!focusedShadow) return false + const stumpParticle = focusedShadow.getShadowStumpParticle() + return stumpParticle && stumpParticle.isInputType() + } + copyTextToClipboard(text) {} + setCopyData(evt, str) {} + getAppWebPageUrl() { + return this._fullHtmlPageUrlIncludingProtocolAndFileName + } + getAppWebPageParentFolderWithoutTrailingSlash() { + return Utils.getPathWithoutFileName(this._fullHtmlPageUrlIncludingProtocolAndFileName) + } + _makeRelativeUrlAbsolute(url) { + if (url.startsWith("http://") || url.startsWith("https://")) return url + return this.getAppWebPageParentFolderWithoutTrailingSlash() + "/" + url.replace(/^\//, "") + } + async makeUrlAbsoluteAndHttpGetUrl(url, queryStringObject, responseClass = WillowHTTPResponse) { + return this.httpGetUrl(this._makeRelativeUrlAbsolute(url), queryStringObject, responseClass) + } + async httpGetUrl(url, queryStringObject, responseClass = WillowHTTPResponse) { + if (this._offlineMode) return new WillowHTTPResponse() + const superAgentResponse = await superagent + .get(url) + .query(queryStringObject) + .set(this._headers || {}) + return new responseClass(superAgentResponse) + } + _getFromResponseCache(cacheKey) { + const hit = this._httpGetResponseCache[cacheKey] + if (hit) hit.setFromCache(true) + return hit + } + _setInResponseCache(url, res) { + this._httpGetResponseCache[url] = res + return this + } + async httpGetUrlFromCache(url, queryStringMap = {}, responseClass = WillowHTTPResponse) { + const cacheKey = url + JSON.stringify(queryStringMap) + const cacheHit = this._getFromResponseCache(cacheKey) + if (!cacheHit) { + const res = await this.httpGetUrl(url, queryStringMap, responseClass) + this._setInResponseCache(cacheKey, res) + return res + } + return cacheHit + } + async httpGetUrlFromProxyCache(url) { + const queryStringMap = {} + queryStringMap.url = url + queryStringMap.cacheOnServer = "true" + return await this.httpGetUrlFromCache("/proxy", queryStringMap, WillowHTTPProxyCacheResponse) + } + async httpPostUrl(url, data) { + if (this._offlineMode) return new WillowHTTPResponse() + const superAgentResponse = await superagent + .post(this._makeRelativeUrlAbsolute(url)) + .set(this._headers || {}) + .send(data) + return new WillowHTTPResponse(superAgentResponse) + } + encodeURIComponent(str) { + return encodeURIComponent(str) + } + downloadFile(data, filename, filetype) { + // noop + } + async appendScript(url) {} + getWindowTitle() { + // todo: deep getParticleByBase/withBase/type/word or something? + const particles = this.topDownArray + const titleParticle = particles.find(particle => particle.firstWord === WillowConstants.titleTag) + return titleParticle ? titleParticle.content : "" + } + setWindowTitle(value) { + const particles = this.topDownArray + const headParticle = particles.find(particle => particle.firstWord === WillowConstants.tags.head) + headParticle.touchParticle(WillowConstants.titleTag).setContent(value) + return this + } + _getHostname() { + return this.location.hostname || "" + } + openUrl(link) { + // noop in willow + } + getPageHtml() { + return this.getHtmlStumpParticle().asHtmlWithSuids() + } + getStumpParticleFromElement(el) {} + setPasteHandler(fn) { + return this + } + setErrorHandler(fn) { + return this + } + setCopyHandler(fn) { + return this + } + setCutHandler(fn) { + return this + } + setResizeEndHandler(fn) { + return this + } + async confirmThen(message) { + return true + } + async promptThen(message, value) { + return value + } + setLoadedDroppedFileHandler(callback, helpText = "") {} + getWindowSize() { + return { + width: 1111, + height: 1111 + } + } + getDocumentSize() { + return this.getWindowSize() + } + isExternalLink(link) { + if (link && link.substr(0, 1) === "/") return false + if (!link.includes("//")) return false + const hostname = this._getHostname() + const url = new URL(link) + return url.hostname && hostname !== url.hostname + } + forceRepaint() {} + blurFocusedInput() {} +} +class WillowBrowser extends AbstractWillowBrowser { + constructor(fullHtmlPageUrlIncludingProtocolAndFileName) { + super(fullHtmlPageUrlIncludingProtocolAndFileName) + this._offlineMode = true + } +} +WillowBrowser._stumpsOnPage = 0 +class WillowBrowserShadow extends AbstractWillowShadow { + get element() { + if (!this._cachedEl) this._cachedEl = document.querySelector(`[${WillowConstants.uidAttribute}="${this.getShadowStumpParticle()._getUid()}"]`) + return this._cachedEl + } + getShadowValueFromAttr() { + return this.element.getAttribute(WillowConstants.value) + } + isShadowChecked() { + return this.element.checked + } + getShadowAttr(name) { + return this.element.getAttribute(name) + } + _logMessage(type) { + if (true) return true + WillowBrowserShadow._shadowUpdateNumber++ + console.log(`DOM Update ${WillowBrowserShadow._shadowUpdateNumber}: ${type}`) + } + // BEGIN MUTABLE METHODS: + // todo: add tests + // todo: idea, don't "paint" wall (dont append it to parent, until done.) + insertHtmlParticle(childStumpParticle, index) { + const { domElement } = childStumpParticle + const { element } = this + // todo: can we virtualize this? + // would it be a "virtual shadow?" + if (index === undefined) element.appendChild(domElement) + else if (index === 0) element.prepend(domElement) + else element.insertBefore(domElement, element.children[index]) + WillowBrowser._stumpsOnPage++ + this._logMessage("insert") + } + removeShadow() { + this.element.remove() + WillowBrowser._stumpsOnPage-- + this._logMessage("remove") + return this + } + setInputOrTextAreaValue(value) { + this.element.value = value + this._logMessage("val") + return this + } + setShadowAttr(name, value) { + this.element.setAttribute(name, value) + this._logMessage("attr") + return this + } + getShadowCss(prop) { + const { element } = this + const compStyles = window.getComputedStyle(element) + return compStyles.getPropertyValue(prop) + } + getShadowPosition() { + return this.element.getBoundingClientRect() + } + shadowHasClass(name) { + return this.element.classList.contains(name) + } + getShadowValue() { + // todo: cleanup, add tests + if (this.getShadowStumpParticle().isInputType()) return this.element.value + return this.element.value || this.getShadowValueFromAttr() + } + addClassToShadow(className) { + this.element.classList.add(className) + this._logMessage("addClass") + return this + } + removeClassFromShadow(className) { + this.element.classList.remove(className) + this._logMessage("removeClass") + return this + } + toggleShadow() { + const { element } = this + element.style.display = element.style.display == "none" ? "block" : "none" + this._logMessage("toggle") + return this + } + getShadowOuterHeight() { + return this.element.outerHeight + } + getShadowOuterWidth() { + return this.element.outerWidth + } + getShadowWidth() { + return this.element.innerWidth + } + getShadowHeight() { + return this.element.innerHeight + } + getShadowOffset() { + const element = this.element + if (!element.getClientRects().length) return { top: 0, left: 0 } + const rect = element.getBoundingClientRect() + const win = element.ownerDocument.defaultView + return { + top: rect.top + win.pageYOffset, + left: rect.left + win.pageXOffset + } + } + triggerShadowEvent(event) { + this.element.dispatchEvent(new Event(event)) + this._logMessage("trigger") + return this + } + onShadowEvent(event, fn) { + this.element.addEventListener(event, fn) + this._logMessage("bind on") + return this + } + onShadowEventWithSelector(event, selector, fn) { + this.element.addEventListener(event, function (evt) { + let target = evt.target + while (target !== null) { + if (target.matches(selector)) { + fn(target, evt) + return + } + target = target.parentElement + } + }) + this._logMessage("bind on") + return this + } + offShadowEvent(event, fn) { + this.element.removeEventListener(event, fn) + this._logMessage("bind off") + return this + } +} +WillowBrowserShadow._shadowUpdateNumber = 0 // todo: what is this for, debugging perf? +// same thing, except with side effects. +class RealWillowBrowser extends AbstractWillowBrowser { + findStumpParticlesByShadowClass(className) { + const stumpParticles = [] + const els = document.getElementsByClassName(className) + for (let el of els) { + stumpParticles.push(this.getStumpParticleFromElement(this)) + } + return stumpParticles + } + getElementById(id) { + return document.getElementById(id) + } + setHtmlOfElementWithIdHack(id, html = "") { + document.getElementById(id).innerHTML = html + } + setHtmlOfElementsWithClassHack(className, html = "") { + const els = document.getElementsByClassName(className) + for (let el of els) { + el.innerHTML = html + } + } + setValueOfElementWithIdHack(id, value = "") { + const el = document.getElementById(id) + el.value = value + } + setValueOfElementsWithClassHack(className, value = "") { + const els = document.getElementsByClassName(className) + for (let el of els) { + el.value = value + } + } + getElementByTagName(tagName) { + return document.getElementsByTagName(tagName)[0] + } + addSuidsToHtmlHeadAndBodyShadows() { + this.getElementByTagName(WillowConstants.tags.html).setAttribute(WillowConstants.uidAttribute, this.getHtmlStumpParticle()._getUid()) + this.getElementByTagName(WillowConstants.tags.head).setAttribute(WillowConstants.uidAttribute, this.getHeadStumpParticle()._getUid()) + this.getElementByTagName(WillowConstants.tags.body).setAttribute(WillowConstants.uidAttribute, this.getBodyStumpParticle()._getUid()) + } + getShadowClass() { + return WillowBrowserShadow + } + setCopyHandler(fn) { + document.addEventListener(BrowserEvents.copy, event => { + fn(event) + }) + return this + } + setCutHandler(fn) { + document.addEventListener(BrowserEvents.cut, event => { + fn(event) + }) + return this + } + setPasteHandler(fn) { + window.addEventListener(BrowserEvents.paste, fn, false) + return this + } + setErrorHandler(fn) { + window.addEventListener("error", fn) + window.addEventListener("unhandledrejection", fn) + return this + } + toggleFullScreen() { + const doc = document + if ((doc.fullScreenElement && doc.fullScreenElement !== null) || (!doc.mozFullScreen && !doc.webkitIsFullScreen)) { + if (doc.documentElement.requestFullScreen) doc.documentElement.requestFullScreen() + else if (doc.documentElement.mozRequestFullScreen) doc.documentElement.mozRequestFullScreen() + else if (doc.documentElement.webkitRequestFullScreen) doc.documentElement.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT) + } else { + if (doc.cancelFullScreen) doc.cancelFullScreen() + else if (doc.mozCancelFullScreen) doc.mozCancelFullScreen() + else if (doc.webkitCancelFullScreen) doc.webkitCancelFullScreen() + } + } + setCopyData(evt, str) { + const originalEvent = evt.originalEvent + originalEvent.preventDefault() + originalEvent.clipboardData.setData("text/plain", str) + originalEvent.clipboardData.setData("text/html", str) + } + getMousetrap() { + return window.Mousetrap + } + copyTextToClipboard(text) { + // http://stackoverflow.com/questions/400212/how-do-i-copy-to-the-clipboard-in-javascript + const textArea = document.createElement("textarea") + textArea.style.position = "fixed" + textArea.style.top = "0" + textArea.style.left = "0" + textArea.style.width = "2em" + textArea.style.height = "2em" + textArea.style.padding = "0" + textArea.style.border = "none" + textArea.style.outline = "none" + textArea.style.boxShadow = "none" + textArea.style.background = "transparent" + textArea.value = text + document.body.appendChild(textArea) + textArea.select() + try { + const successful = document.execCommand("copy") + } catch (err) {} + document.body.removeChild(textArea) + } + getStore() { + return window.store + } + getHash() { + return location.hash || "" + } + setHash(value) { + location.hash = value + } + getHost() { + return location.host + } + _getHostname() { + return location.hostname + } + async appendScript(url) { + if (!url) return undefined + if (!this._loadingPromises) this._loadingPromises = {} + if (this._loadingPromises[url]) return this._loadingPromises[url] + if (this.isNodeJs()) return undefined + this._loadingPromises[url] = this._appendScript(url) + return this._loadingPromises[url] + } + _appendScript(url) { + //https://bradb.net/blog/promise-based-js-script-loader/ + return new Promise(function (resolve, reject) { + let resolved = false + const scriptEl = document.createElement("script") + scriptEl.type = "text/javascript" + scriptEl.src = url + scriptEl.async = true + scriptEl.onload = scriptEl.onreadystatechange = function () { + if (!resolved && (!this.readyState || this.readyState == "complete")) { + resolved = true + resolve(this) + } + } + scriptEl.onerror = scriptEl.onabort = reject + document.head.appendChild(scriptEl) + }) + } + downloadFile(data, filename, filetype) { + const downloadLink = document.createElement("a") + downloadLink.setAttribute("href", `data:${filetype},` + encodeURIComponent(data)) + downloadLink.setAttribute("download", filename) + downloadLink.click() + } + reload() { + window.location.reload() + } + openUrl(link) { + window.open(link) + } + setResizeEndHandler(fn) { + let resizeTimer + window.addEventListener(BrowserEvents.resize, evt => { + const target = evt.target + if (target !== window) return // dont resize on div resizes + clearTimeout(resizeTimer) + resizeTimer = setTimeout(() => { + fn(this.getWindowSize()) + }, 100) + }) + return this + } + getStumpParticleFromElement(el) { + return this.getHtmlStumpParticle().getParticleByGuid(parseInt(el.getAttribute(WillowConstants.uidAttribute))) + } + forceRepaint() { + // todo: + } + getBrowserHtml() { + return document.documentElement.outerHTML + } + async confirmThen(message) { + return confirm(message) + } + async promptThen(message, value) { + return prompt(message, value) + } + getWindowSize() { + return { + width: window.innerWidth, + height: window.innerHeight + } + } + // todo: denote the side effect + blurFocusedInput() { + // todo: test against browser. + document.activeElement.blur() + } + setLoadedDroppedFileHandler(callback, helpText = "") { + const bodyStumpParticle = this.getBodyStumpParticle() + const bodyShadow = bodyStumpParticle.getShadow() + // Added the below to ensure dragging from the chrome downloads bar works + // http://stackoverflow.com/questions/19526430/drag-and-drop-file-uploads-from-chrome-downloads-bar + const handleChromeBug = event => { + const originalEvent = event.originalEvent + const effect = originalEvent.dataTransfer.effectAllowed + originalEvent.dataTransfer.dropEffect = effect === "move" || effect === "linkMove" ? "move" : "copy" + } + const dragoverHandler = event => { + handleChromeBug(event) + event.preventDefault() + event.stopPropagation() + if (!bodyStumpParticle.stumpParticleHasClass("dragOver")) { + bodyStumpParticle.insertChildParticle(`div ${helpText} + id dragOverHelp`) + bodyStumpParticle.addClassToStumpParticle("dragOver") + // Add the help, and then hopefull we'll get a dragover event on the dragOverHelp, then + // 50ms later, add the dragleave handler, and from now on drag leave will only happen on the help + // div + setTimeout(function () { + bodyShadow.onShadowEvent(BrowserEvents.dragleave, dragleaveHandler) + }, 50) + } + } + const dragleaveHandler = event => { + event.preventDefault() + event.stopPropagation() + bodyStumpParticle.removeClassFromStumpParticle("dragOver") + bodyStumpParticle.findStumpParticleByChild("id dragOverHelp").removeStumpParticle() + bodyShadow.offShadowEvent(BrowserEvents.dragleave, dragleaveHandler) + } + const dropHandler = async event => { + event.preventDefault() + event.stopPropagation() + bodyStumpParticle.removeClassFromStumpParticle("dragOver") + bodyStumpParticle.findStumpParticleByChild("id dragOverHelp").removeStumpParticle() + const droppedItems = event.originalEvent.dataTransfer.items + // NOTE: YOU NEED TO STAY IN THE "DROP" EVENT, OTHERWISE THE DATATRANSFERITEMS MUTATE + // (BY DESIGN) https://bugs.chromium.org/p/chromium/issues/detail?id=137231 + // DO NOT ADD AN AWAIT IN THIS LOOP. IT WILL BREAK. + const items = [] + for (let droppedItem of droppedItems) { + const entry = droppedItem.webkitGetAsEntry() + items.push(this._handleDroppedEntry(entry)) + } + const results = await Promise.all(items) + callback(results) + } + bodyShadow.onShadowEvent(BrowserEvents.dragover, dragoverHandler) + bodyShadow.onShadowEvent(BrowserEvents.drop, dropHandler) + // todo: why do we do this? + bodyShadow.onShadowEvent(BrowserEvents.dragenter, function (event) { + event.preventDefault() + event.stopPropagation() + }) + } + _handleDroppedEntry(item, path = "") { + // http://stackoverflow.com/questions/3590058/does-html5-allow-drag-drop-upload-of-folders-or-a-folder-tree + // http://stackoverflow.com/questions/6756583/prevent-browser-from-loading-a-drag-and-dropped-file + return item.isFile ? this._handleDroppedFile(item) : this._handleDroppedDirectory(item, path) + } + _handleDroppedDirectory(item, path) { + return new Promise((resolve, reject) => { + item.createReader().readEntries(async entries => { + const promises = [] + for (let i = 0; i < entries.length; i++) { + promises.push(this._handleDroppedEntry(entries[i], path + item.name + "/")) + } + const res = await Promise.all(promises) + resolve(res) + }) + }) + } + _handleDroppedFile(file) { + // https://developer.mozilla.org/en-US/docs/Using_files_from_web_applications + // http://www.sitepoint.com/html5-javascript-open-dropped-files/ + return new Promise((resolve, reject) => { + file.file(data => { + const reader = new FileReader() + reader.onload = evt => { + resolve({ data: evt.target.result, filename: data.name }) + } + reader.onerror = err => reject(err) + reader.readAsText(data) + }) + }) + } + _getFocusedShadow() { + const stumpParticle = this.getStumpParticleFromElement(document.activeElement) + return stumpParticle && stumpParticle.getShadow() + } +} +class AbstractTheme { + hakonToCss(str) { + const hakonProgram = new hakonParser(str) + // console.log(hakonProgram.getAllErrors()) + return hakonProgram.compile() + } +} +class DefaultTheme extends AbstractTheme {} +class AbstractParticleComponentParser extends ParserBackedParticle { + async startWhenReady() { + if (this.isNodeJs()) return this.start() + document.addEventListener( + "DOMContentLoaded", + async () => { + this.start() + }, + false + ) + } + start() { + this._bindParticleComponentFrameworkCommandListenersOnBody() + this.renderAndGetRenderReport(this.willowBrowser.getBodyStumpParticle()) + } + get willowBrowser() { + if (!this._willowBrowser) { + if (this.isNodeJs()) { + this._willowBrowser = new WillowBrowser("http://localhost:8000/index.html") + } else { + this._willowBrowser = new RealWillowBrowser(window.location.href) + } + } + return this._willowBrowser + } + onCommandError(err) { + throw err + } + _setMouseEvent(evt) { + this._mouseEvent = evt + return this + } + getMouseEvent() { + return this._mouseEvent || this.willowBrowser.getMockMouseEvent() + } + _onCommandWillRun() { + // todo: remove. currently used by ohayo + } + _getCommandArgumentsFromStumpParticle(stumpParticle, commandMethod) { + if (commandMethod.includes(" ")) { + // todo: cleanup and document + // It seems the command arguments can from the method string or from form values. + const parts = commandMethod.split(" ") + return { + uno: parts[1], + dos: parts[2] + } + } + const shadow = stumpParticle.getShadow() + let valueParam + if (stumpParticle.isStumpParticleCheckbox()) valueParam = shadow.isShadowChecked() ? true : false + // todo: fix bug if nothing is entered. + else if (shadow.getShadowValue() !== undefined) valueParam = shadow.getShadowValue() + else valueParam = stumpParticle.getStumpParticleAttr("value") + const nameParam = stumpParticle.getStumpParticleAttr("name") + return { + uno: valueParam, + dos: nameParam + } + } + getStumpParticleString() { + return this.willowBrowser.getHtmlStumpParticle().toString() + } + _getHtmlOnlyParticles() { + const particles = [] + this.willowBrowser.getHtmlStumpParticle().deepVisit(particle => { + if (particle.firstWord === "styleTag" || (particle.content || "").startsWith(" { + if (particle.firstWord === "styleTag" || (particle.content || "").startsWith(" particle.getTextContent()) + .filter(text => text) + .join("\n") + } + getCommandNames() { + return Object.getOwnPropertyNames(Object.getPrototypeOf(this)).filter(word => word.endsWith("Command")) + } + async _executeCommandOnStumpParticle(stumpParticle, commandMethod) { + const params = this._getCommandArgumentsFromStumpParticle(stumpParticle, commandMethod) + if (commandMethod.includes(" ")) + // todo: cleanup + commandMethod = commandMethod.split(" ")[0] + this.addToCommandLog([commandMethod, params.uno, params.dos].filter(identity => identity).join(" ")) + this._onCommandWillRun() // todo: remove. currently used by ohayo + let particleComponent = stumpParticle.getStumpParticleParticleComponent() + while (!particleComponent[commandMethod]) { + const parent = particleComponent.parent + if (parent === particleComponent) throw new Error(`Unknown command "${commandMethod}"`) + if (!parent) debugger + particleComponent = parent + } + try { + await particleComponent[commandMethod](params.uno, params.dos) + } catch (err) { + this.onCommandError(err) + } + } + _bindParticleComponentFrameworkCommandListenersOnBody() { + const willowBrowser = this.willowBrowser + const bodyShadow = willowBrowser.getBodyStumpParticle().getShadow() + const app = this + const checkAndExecute = (el, attr, evt) => { + const stumpParticle = willowBrowser.getStumpParticleFromElement(el) + evt.preventDefault() + evt.stopImmediatePropagation() + this._executeCommandOnStumpParticle(stumpParticle, stumpParticle.getStumpParticleAttr(attr)) + return false + } + bodyShadow.onShadowEventWithSelector(BrowserEvents.contextmenu, `[${WillowConstants.contextMenuCommand}]`, function (target, evt) { + if (evt.ctrlKey) return true + app._setMouseEvent(evt) // todo: remove? + return checkAndExecute(target, WillowConstants.contextMenuCommand, evt) + }) + bodyShadow.onShadowEventWithSelector(BrowserEvents.click, `[${WillowConstants.clickCommand}]`, function (target, evt) { + if (evt.shiftKey) return checkAndExecute(this, WillowConstants.shiftClickCommand, evt) + app._setMouseEvent(evt) // todo: remove? + return checkAndExecute(target, WillowConstants.clickCommand, evt) + }) + bodyShadow.onShadowEventWithSelector(BrowserEvents.dblclick, `[${WillowConstants.doubleClickCommand}]`, function (target, evt) { + if (evt.target !== evt.currentTarget) return true // direct dblclicks only + app._setMouseEvent(evt) // todo: remove? + return checkAndExecute(target, WillowConstants.doubleClickCommand, evt) + }) + bodyShadow.onShadowEventWithSelector(BrowserEvents.blur, `[${WillowConstants.blurCommand}]`, function (target, evt) { + return checkAndExecute(target, WillowConstants.blurCommand, evt) + }) + bodyShadow.onShadowEventWithSelector(BrowserEvents.keyup, `[${WillowConstants.keyUpCommand}]`, function (target, evt) { + return checkAndExecute(target, WillowConstants.keyUpCommand, evt) + }) + bodyShadow.onShadowEventWithSelector(BrowserEvents.change, `[${WillowConstants.changeCommand}]`, function (target, evt) { + return checkAndExecute(target, WillowConstants.changeCommand, evt) + }) + } + stopPropagationCommand() { + // todo: remove? + // intentional noop + } + // todo: remove? + async clearMessageBufferCommand() { + delete this._messageBuffer + } + // todo: remove? + async unmountAndDestroyCommand() { + this.unmountAndDestroy() + } + toggleParticleComponentFrameworkDebuggerCommand() { + // todo: move somewhere else? + // todo: cleanup + const app = this.root + const particle = app.getParticle("ParticleComponentFrameworkDebuggerComponent") + if (particle) { + particle.unmountAndDestroy() + } else { + app.appendLine("ParticleComponentFrameworkDebuggerComponent") + app.renderAndGetRenderReport() + } + } + getStumpParticle() { + return this._htmlStumpParticle + } + toHakonCode() { + return "" + } + getTheme() { + if (!this.isRoot()) return this.root.getTheme() + if (!this._theme) this._theme = new DefaultTheme() + return this._theme + } + getCommandsBuffer() { + if (!this._commandsBuffer) this._commandsBuffer = [] + return this._commandsBuffer + } + addToCommandLog(command) { + this.getCommandsBuffer().push({ + command: command, + time: this._getProcessTimeInMilliseconds() + }) + } + getMessageBuffer() { + if (!this._messageBuffer) this._messageBuffer = new Particle() + return this._messageBuffer + } + // todo: move this to particle class? or other higher level class? + addStumpCodeMessageToLog(message) { + // note: we have 1 parameter, and are going to do type inference first. + // Todo: add actions that can be taken from a message? + // todo: add tests + this.getMessageBuffer().appendLineAndChildren("message", message) + } + addStumpErrorMessageToLog(errorMessage) { + // todo: cleanup! + return this.addStumpCodeMessageToLog(`div + class OhayoError + bern${Particle.nest(errorMessage, 2)}`) + } + logMessageText(message = "") { + const pre = `pre + bern${Particle.nest(message, 2)}` + return this.addStumpCodeMessageToLog(pre) + } + unmount() { + if ( + !this.isMounted() // todo: why do we need this check? + ) + return undefined + this._getChildParticleComponents().forEach(child => child.unmount()) + this.particleComponentWillUnmount() + this._removeCss() + this._removeHtml() + delete this._lastRenderedTime + this.particleComponentDidUnmount() + } + _removeHtml() { + this._htmlStumpParticle.removeStumpParticle() + delete this._htmlStumpParticle + } + toStumpCode() { + return `div + class ${this.getCssClassNames().join(" ")}` + } + getCssClassNames() { + return this._getJavascriptPrototypeChainUpTo("AbstractParticleComponentParser") + } + particleComponentWillMount() {} + async particleComponentDidMount() { + AbstractParticleComponentParser._mountedParticleComponents++ + } + particleComponentDidUnmount() { + AbstractParticleComponentParser._mountedParticleComponents-- + } + particleComponentWillUnmount() {} + getNewestTimeToRender() { + return this._lastTimeToRender + } + _setLastRenderedTime(time) { + this._lastRenderedTime = time + return this + } + async particleComponentDidUpdate() {} + _getChildParticleComponents() { + return this.getChildrenByParser(AbstractParticleComponentParser) + } + _hasChildrenParticleComponents() { + return this._getChildParticleComponents().length > 0 + } + // todo: this is hacky. we do it so we can just mount all tiles to wall. + getStumpParticleForChildren() { + return this.getStumpParticle() + } + _getLastRenderedTime() { + return this._lastRenderedTime + } + get _css() { + return this.getTheme().hakonToCss(this.toHakonCode()) + } + toPlainHtml(containerId) { + return `
+ +${new stumpParser(this.toStumpCode()).compile()} +
` + } + _updateAndGetUpdateReport() { + const reasonForUpdatingOrNot = this.getWhetherToUpdateAndReason() + if (!reasonForUpdatingOrNot.shouldUpdate) return reasonForUpdatingOrNot + this._setLastRenderedTime(this._getProcessTimeInMilliseconds()) + this._removeCss() + this._mountCss() + // todo: fucking switch to react? looks like we don't update parent because we dont want to nuke children. + // okay. i see why we might do that for non tile particleComponents. but for Tile particleComponents, seems like we arent nesting, so why not? + // for now + if (this._hasChildrenParticleComponents()) return { shouldUpdate: false, reason: "did not update because is a parent" } + this._updateHtml() + this._lastTimeToRender = this._getProcessTimeInMilliseconds() - this._getLastRenderedTime() + return reasonForUpdatingOrNot + } + _updateHtml() { + const stumpParticleToMountOn = this._htmlStumpParticle.parent + const currentIndex = this._htmlStumpParticle.getIndex() + this._removeHtml() + this._mountHtml(stumpParticleToMountOn, this._toLoadedOrLoadingStumpCode(), currentIndex) + } + unmountAndDestroy() { + this.unmount() + return this.destroy() + } + // todo: move to keyword particle class? + toggle(firstWord, contentOptions) { + const currentParticle = this.getParticle(firstWord) + if (!contentOptions) return currentParticle ? currentParticle.unmountAndDestroy() : this.appendLine(firstWord) + const currentContent = currentParticle === undefined ? undefined : currentParticle.content + const index = contentOptions.indexOf(currentContent) + const newContent = index === -1 || index + 1 === contentOptions.length ? contentOptions[0] : contentOptions[index + 1] + this.delete(firstWord) + if (newContent) this.touchParticle(firstWord).setContent(newContent) + return newContent + } + isMounted() { + return !!this._htmlStumpParticle + } + toggleAndRender(firstWord, contentOptions) { + this.toggle(firstWord, contentOptions) + this.root.renderAndGetRenderReport() + } + _getFirstOutdatedDependency(lastRenderedTime = this._getLastRenderedTime() || 0) { + return this.getDependencies().find(dep => dep.getLineModifiedTime() > lastRenderedTime) + } + getWhetherToUpdateAndReason() { + const mTime = this.getLineModifiedTime() + const lastRenderedTime = this._getLastRenderedTime() || 0 + const staleTime = mTime - lastRenderedTime + if (lastRenderedTime === 0) + return { + shouldUpdate: true, + reason: "shouldUpdate because this ParticleComponent hasn't been rendered yet", + staleTime: staleTime + } + if (staleTime > 0) + return { + shouldUpdate: true, + reason: "shouldUpdate because this ParticleComponent changed", + staleTime: staleTime + } + const outdatedDependency = this._getFirstOutdatedDependency(lastRenderedTime) + if (outdatedDependency) + return { + shouldUpdate: true, + reason: "Should update because a dependency updated", + dependency: outdatedDependency, + staleTime: outdatedDependency.getLineModifiedTime() - lastRenderedTime + } + return { + shouldUpdate: false, + reason: "Should NOT update because no dependency changed", + lastRenderedTime: lastRenderedTime, + mTime: mTime + } + } + getDependencies() { + return [] + } + _getParticleComponentsThatNeedRendering(arr) { + this._getChildParticleComponents().forEach(child => { + const reasonForUpdatingOrNot = child.getWhetherToUpdateAndReason() + if (!child.isMounted() || reasonForUpdatingOrNot.shouldUpdate) arr.push({ child: child, childUpdateBecause: reasonForUpdatingOrNot }) + child._getParticleComponentsThatNeedRendering(arr) + }) + } + toStumpLoadingCode() { + return `div Loading ${this.firstWord}... + class ${this.getCssClassNames().join(" ")} + id ${this.getParticleComponentId()}` + } + getParticleComponentId() { + // html ids can't begin with a number + return "particleComponent" + this._getUid() + } + _toLoadedOrLoadingStumpCode() { + if (!this.isLoaded()) return this.toStumpLoadingCode() + this.setRunTimePhaseError("renderPhase") + try { + return this.toStumpCode() + } catch (err) { + console.error(err) + this.setRunTimePhaseError("renderPhase", err) + return this.toStumpErrorStateCode(err) + } + } + toStumpErrorStateCode(err) { + return `div ${err} + class ${this.getCssClassNames().join(" ")} + id ${this.getParticleComponentId()}` + } + _mount(stumpParticleToMountOn, index) { + this._setLastRenderedTime(this._getProcessTimeInMilliseconds()) + this.particleComponentWillMount() + this._mountCss() + this._mountHtml(stumpParticleToMountOn, this._toLoadedOrLoadingStumpCode(), index) // todo: add index back? + this._lastTimeToRender = this._getProcessTimeInMilliseconds() - this._getLastRenderedTime() + return this + } + // todo: we might be able to squeeze virtual dom in here on the mountCss and mountHtml methods. + _mountCss() { + const css = this._css + if (!css) return this + // todo: only insert css once per class? have a set? + this._cssStumpParticle = this._getPageHeadStump().insertCssChildParticle(`styleTag + for ${this.constructor.name} + bern${Particle.nest(css, 2)}`) + } + _getPageHeadStump() { + return this.root.willowBrowser.getHeadStumpParticle() + } + _removeCss() { + if (!this._cssStumpParticle) return this + this._cssStumpParticle.removeCssStumpParticle() + delete this._cssStumpParticle + } + _mountHtml(stumpParticleToMountOn, htmlCode, index) { + this._htmlStumpParticle = stumpParticleToMountOn.insertChildParticle(htmlCode, index) + this._htmlStumpParticle.setStumpParticleParticleComponent(this) + } + renderAndGetRenderReport(stumpParticle, index) { + const isUpdateOp = this.isMounted() + let particleComponentUpdateReport = { + shouldUpdate: false, + reason: "" + } + if (isUpdateOp) particleComponentUpdateReport = this._updateAndGetUpdateReport() + else this._mount(stumpParticle, index) + const stumpParticleForChildren = this.getStumpParticleForChildren() + // Todo: insert delayed rendering? + const childResults = this._getChildParticleComponents().map((child, index) => child.renderAndGetRenderReport(stumpParticleForChildren, index)) + if (isUpdateOp) { + if (particleComponentUpdateReport.shouldUpdate) { + try { + if (this.isLoaded()) this.particleComponentDidUpdate() + } catch (err) { + console.error(err) + } + } + } else { + try { + if (this.isLoaded()) this.particleComponentDidMount() + } catch (err) { + console.error(err) + } + } + let str = `${this.getWord(0) || this.constructor.name} ${isUpdateOp ? "update" : "mount"} ${particleComponentUpdateReport.shouldUpdate} ${particleComponentUpdateReport.reason}` + childResults.forEach(child => (str += "\n" + child.toString(1))) + return new Particle(str) + } +} +AbstractParticleComponentParser._mountedParticleComponents = 0 +class ParticleComponentFrameworkDebuggerComponent extends AbstractParticleComponentParser { + toHakonCode() { + return `.ParticleComponentFrameworkDebuggerComponent + position fixed + top 5px + left 5px + z-index 1000 + background rgba(254,255,156, .95) + box-shadow 1px 1px 1px rgba(0,0,0,.5) + padding 12px + overflow scroll + max-height 500px +.ParticleComponentFrameworkDebuggerComponentCloseButton + position absolute + cursor pointer + opacity .9 + top 2px + right 2px + &:hover + opacity 1` + } + toStumpCode() { + const app = this.root + return `div + class ParticleComponentFrameworkDebuggerComponent + div x + class ParticleComponentFrameworkDebuggerComponentCloseButton + clickCommand toggleParticleComponentFrameworkDebuggerCommand + div + span This app is powered by the + a ParticleComponentFramework + href https://github.com/breck7/scrollsdk/tree/main/particleComponentFramework + p ${app.numberOfLines} components loaded. ${WillowBrowser._stumpsOnPage} stumps on page. + pre + bern +${app.toString(3)}` + } +} +class AbstractGithubTriangleComponent extends AbstractParticleComponentParser { + constructor() { + super(...arguments) + this.githubLink = `https://github.com/breck7/scrollsdk` + } + toHakonCode() { + return `.AbstractGithubTriangleComponent + display block + position absolute + top 0 + right 0` + } + toStumpCode() { + return `a + class AbstractGithubTriangleComponent + href ${this.githubLink} + img + src ../images/github-fork.svg` + } +} +window.AbstractGithubTriangleComponent = AbstractGithubTriangleComponent +window.AbstractParticleComponentParser = AbstractParticleComponentParser +window.WillowBrowser = WillowBrowser +window.ParticleComponentFrameworkDebuggerComponent = ParticleComponentFrameworkDebuggerComponent diff --git a/node_modules/scroll-cli/package.json b/node_modules/scroll-cli/package.json index 4d62b56191..46c68165ba 100644 --- a/node_modules/scroll-cli/package.json +++ b/node_modules/scroll-cli/package.json @@ -1,6 +1,6 @@ { "name": "scroll-cli", - "version": "126.0.1", + "version": "126.1.0", "description": "A language for scientists of all ages. A curated collection of tools for refining and sharing thoughts.", "main": "scroll.js", "engines": { diff --git a/node_modules/scroll-cli/parsers/forms.parsers b/node_modules/scroll-cli/parsers/forms.parsers index 3500773f8b..fe4c028ff3 100644 --- a/node_modules/scroll-cli/parsers/forms.parsers +++ b/node_modules/scroll-cli/parsers/forms.parsers @@ -45,9 +45,25 @@ scrollFormParser extends classicFormParser crux scrollForm description Generate a Scroll Form. + string copyFromExternal codeMirror.css scrollLibs.js + string requireOnce + + javascript get inputs() { const placeholder = "" const Name = "scroll" - return `` + return ` + + ` + } + compile(compileSettings) { + return this.getHtmlRequirements(compileSettings) + super.compile() } diff --git a/node_modules/scroll-cli/readme.scroll b/node_modules/scroll-cli/readme.scroll index d9d3e8b9be..e3d55628c5 100644 --- a/node_modules/scroll-cli/readme.scroll +++ b/node_modules/scroll-cli/readme.scroll @@ -7,7 +7,7 @@ css html {font-size: var(--base-font-size, 18px);} printTitle # Evolve and publish your most intelligent ideas -
+br mediumColumns 1 try.scroll.pub    ScrollHub diff --git a/node_modules/scroll-cli/scroll.js b/node_modules/scroll-cli/scroll.js index 968772fff0..17030bfd39 100755 --- a/node_modules/scroll-cli/scroll.js +++ b/node_modules/scroll-cli/scroll.js @@ -334,6 +334,31 @@ class ScrollFile { return this._measures } + get parsersBundle() { + let code = + `parsers/cellTypes.parsers +parsers/root.parsers +parsers/build.parsers +parsers/comments.parsers +parsers/blankLine.parsers +parsers/measures.parsers +parsers/errors.parsers` + .trim() + .split("\n") + .map(filepath => Disk.read(path.join(__dirname, filepath))) + .join("\n\n") + .replace("catchAllParser catchAllParagraphParser", "catchAllParser errorParser") + this.scrollProgram.toString() + + code = code.replace(/^importOnly\n/gm, "").replace(/^import .+/gm, "") + + code = new Particle(code) + code.getParticle("commentParser").appendLine("boolean suggestInAutocomplete false") + code.getParticle("slashCommentParser").appendLine("boolean suggestInAutocomplete false") + code.getParticle("buildMeasuresParser").appendLine("boolean suggestInAutocomplete false") + + return code.toString() + } + get tables() { return this.scrollProgram.filter(particle => particle.isTabularData && particle.isFirst) } diff --git a/package-lock.json b/package-lock.json index e390976382..9b2d9cc2b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "2.0.0", "devDependencies": { "lodash": "^4.17.21", - "scroll-cli": "^126.0.1", + "scroll-cli": "^126.1.0", "scrollsdk": "^84.0.0" } }, @@ -415,9 +415,9 @@ "dev": true }, "node_modules/scroll-cli": { - "version": "126.0.1", - "resolved": "https://registry.npmjs.org/scroll-cli/-/scroll-cli-126.0.1.tgz", - "integrity": "sha512-AEwozXRNPV4qfXZWlcozIMoh7kwSP7i1qXgEZvlGya6nGJ32bSxZ1lGjxxCI6zRmwJ0Esfkoz9cdGDx8thLPrQ==", + "version": "126.1.0", + "resolved": "https://registry.npmjs.org/scroll-cli/-/scroll-cli-126.1.0.tgz", + "integrity": "sha512-l+k7BmlTk54jqALevlM9bHFSOogWXPe+p5wColKZFOsSoJsk9jdSBCKAwnG8OQpMOldmCmzRUy6lVEWRlVkyNQ==", "dev": true, "dependencies": { "d3": "^6.7.0", diff --git a/package.json b/package.json index e3726c384c..4f397a5668 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "homepage": "https://cancerdb.com", "devDependencies": { "lodash": "^4.17.21", - "scroll-cli": "^126.0.1", + "scroll-cli": "^126.1.0", "scrollsdk": "^84.0.0" } } diff --git a/pages/about.html b/pages/about.html index 87862b7224..a4cbd6f741 100644 --- a/pages/about.html +++ b/pages/about.html @@ -12,12 +12,12 @@ About - + - + diff --git a/pages/acknowledgements.html b/pages/acknowledgements.html index 28740b1f02..55d360a7c8 100644 --- a/pages/acknowledgements.html +++ b/pages/acknowledgements.html @@ -12,12 +12,12 @@ Acknowledgements - + - + diff --git a/pages/explore.html b/pages/explore.html index 623a718740..5188dacfab 100644 --- a/pages/explore.html +++ b/pages/explore.html @@ -12,12 +12,12 @@ Explore CancerDB - + - + diff --git a/scrollLibs.js b/scrollLibs.js new file mode 100644 index 0000000000..18951d6de3 --- /dev/null +++ b/scrollLibs.js @@ -0,0 +1,19159 @@ +/* mousetrap v1.6.3 craig.is/killing/mice */ +(function(q,u,c){function v(a,b,g){a.addEventListener?a.addEventListener(b,g,!1):a.attachEvent("on"+b,g)}function z(a){if("keypress"==a.type){var b=String.fromCharCode(a.which);a.shiftKey||(b=b.toLowerCase());return b}return n[a.which]?n[a.which]:r[a.which]?r[a.which]:String.fromCharCode(a.which).toLowerCase()}function F(a){var b=[];a.shiftKey&&b.push("shift");a.altKey&&b.push("alt");a.ctrlKey&&b.push("ctrl");a.metaKey&&b.push("meta");return b}function w(a){return"shift"==a||"ctrl"==a||"alt"==a|| +"meta"==a}function A(a,b){var g,d=[];var e=a;"+"===e?e=["+"]:(e=e.replace(/\+{2}/g,"+plus"),e=e.split("+"));for(g=0;gc||n.hasOwnProperty(c)&&(p[n[c]]=c)}g=p[e]?"keydown":"keypress"}"keypress"==g&&d.length&&(g="keydown");return{key:m,modifiers:d,action:g}}function D(a,b){return null===a||a===u?!1:a===b?!0:D(a.parentNode,b)}function d(a){function b(a){a= +a||{};var b=!1,l;for(l in p)a[l]?b=!0:p[l]=0;b||(x=!1)}function g(a,b,t,f,g,d){var l,E=[],h=t.type;if(!k._callbacks[a])return[];"keyup"==h&&w(a)&&(b=[a]);for(l=0;l":".","?":"/","|":"\\"},B={option:"alt",command:"meta","return":"enter", +escape:"esc",plus:"+",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},p;for(c=1;20>c;++c)n[111+c]="f"+c;for(c=0;9>=c;++c)n[c+96]=c.toString();d.prototype.bind=function(a,b,c){a=a instanceof Array?a:[a];this._bindMultiple.call(this,a,b,c);return this};d.prototype.unbind=function(a,b){return this.bind.call(this,a,function(){},b)};d.prototype.trigger=function(a,b){if(this._directMap[a+":"+b])this._directMap[a+":"+b]({},a);return this};d.prototype.reset=function(){this._callbacks={}; +this._directMap={};return this};d.prototype.stopCallback=function(a,b){if(-1<(" "+b.className+" ").indexOf(" mousetrap ")||D(b,this.target))return!1;if("composedPath"in a&&"function"===typeof a.composedPath){var c=a.composedPath()[0];c!==a.target&&(b=c)}return"INPUT"==b.tagName||"SELECT"==b.tagName||"TEXTAREA"==b.tagName||b.isContentEditable};d.prototype.handleKey=function(){return this._handleKey.apply(this,arguments)};d.addKeycodes=function(a){for(var b in a)a.hasOwnProperty(b)&&(n[b]=a[b]);p=null}; +d.init=function(){var a=d(u),b;for(b in a)"_"!==b.charAt(0)&&(d[b]=function(b){return function(){return a[b].apply(a,arguments)}}(b))};d.init();q.Mousetrap=d;"undefined"!==typeof module&&module.exports&&(module.exports=d);"function"===typeof define&&define.amd&&define(function(){return d})}})("undefined"!==typeof window?window:null,"undefined"!==typeof window?document:null); + +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0",options:{classes:{},disabled:!1,create:null},_createWidget:function(t,e){e=b(e||this.defaultElement||this)[0],this.element=b(e),this.uuid=s++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=b(),this.hoverable=b(),this.focusable=b(),this.classesElementLookup={},e!==this&&(b.data(e,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===e&&this.destroy()}}),this.document=b(e.style?e.ownerDocument:e.document||e),this.window=b(this.document[0].defaultView||this.document[0].parentWindow)),this.options=b.widget.extend({},this.options,this._getCreateOptions(),t),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:b.noop,_create:b.noop,_init:b.noop,destroy:function(){var s=this;this._destroy(),b.each(this.classesElementLookup,function(t,e){s._removeClass(e,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:b.noop,widget:function(){return this.element},option:function(t,e){var s,i,o,n=t;if(0===arguments.length)return b.widget.extend({},this.options);if("string"==typeof t)if(n={},t=(s=t.split(".")).shift(),s.length){for(i=n[t]=b.widget.extend({},this.options[t]),o=0;o=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}}),b.ui.plugin={add:function(t,e,s){var i,o=b.ui[t].prototype;for(i in s)o.plugins[i]=o.plugins[i]||[],o.plugins[i].push([e,s[i]])},call:function(t,e,s,i){var o,n=t.plugins[e];if(n&&(i||t.element[0].parentNode&&11!==t.element[0].parentNode.nodeType))for(o=0;o").css("position","absolute").appendTo(t.parent()).outerWidth(t.outerWidth()).outerHeight(t.outerHeight()).offset(t.offset())[0]})},_unblockFrames:function(){this.iframeBlocks&&(this.iframeBlocks.remove(),delete this.iframeBlocks)},_blurActiveElement:function(t){var e=b.ui.safeActiveElement(this.document[0]);b(t.target).closest(e).length||b.ui.safeBlur(e)},_mouseStart:function(t){var e=this.options;return this.helper=this._createHelper(t),this._addClass(this.helper,"ui-draggable-dragging"),this._cacheHelperProportions(),b.ui.ddmanager&&(b.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(!0),this.offsetParent=this.helper.offsetParent(),this.hasFixedAncestor=0s[2]&&(n=s[2]+this.offset.click.left),t.pageY-this.offset.click.top>s[3]&&(r=s[3]+this.offset.click.top)),i.grid&&(t=i.grid[1]?this.originalPageY+Math.round((r-this.originalPageY)/i.grid[1])*i.grid[1]:this.originalPageY,r=!s||t-this.offset.click.top>=s[1]||t-this.offset.click.top>s[3]?t:t-this.offset.click.top>=s[1]?t-i.grid[1]:t+i.grid[1],t=i.grid[0]?this.originalPageX+Math.round((n-this.originalPageX)/i.grid[0])*i.grid[0]:this.originalPageX,n=!s||t-this.offset.click.left>=s[0]||t-this.offset.click.left>s[2]?t:t-this.offset.click.left>=s[0]?t-i.grid[0]:t+i.grid[0]),"y"===i.axis&&(n=this.originalPageX),"x"===i.axis&&(r=this.originalPageY)),{top:r-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.offset.scroll.top:o?0:this.offset.scroll.top),left:n-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.offset.scroll.left:o?0:this.offset.scroll.left)}},_clear:function(){this._removeClass(this.helper,"ui-draggable-dragging"),this.helper[0]===this.element[0]||this.cancelHelperRemoval||this.helper.remove(),this.helper=null,this.cancelHelperRemoval=!1,this.destroyOnClear&&this.destroy()},_trigger:function(t,e,s){return s=s||this._uiHash(),b.ui.plugin.call(this,t,[e,s,this],!0),/^(drag|start|stop)/.test(t)&&(this.positionAbs=this._convertPositionTo("absolute"),s.offset=this.positionAbs),b.Widget.prototype._trigger.call(this,t,e,s)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}}),b.ui.plugin.add("draggable","connectToSortable",{start:function(e,t,s){var i=b.extend({},t,{item:s.element});s.sortables=[],b(s.options.connectToSortable).each(function(){var t=b(this).sortable("instance");t&&!t.options.disabled&&(s.sortables.push(t),t.refreshPositions(),t._trigger("activate",e,i))})},stop:function(e,t,s){var i=b.extend({},t,{item:s.element});s.cancelHelperRemoval=!1,b.each(s.sortables,function(){var t=this;t.isOver?(t.isOver=0,s.cancelHelperRemoval=!0,t.cancelHelperRemoval=!1,t._storedCSS={position:t.placeholder.css("position"),top:t.placeholder.css("top"),left:t.placeholder.css("left")},t._mouseStop(e),t.options.helper=t.options._helper):(t.cancelHelperRemoval=!0,t._trigger("deactivate",e,i))})},drag:function(s,i,o){b.each(o.sortables,function(){var t=!1,e=this;e.positionAbs=o.positionAbs,e.helperProportions=o.helperProportions,e.offset.click=o.offset.click,e._intersectsWith(e.containerCache)&&(t=!0,b.each(o.sortables,function(){return this.positionAbs=o.positionAbs,this.helperProportions=o.helperProportions,this.offset.click=o.offset.click,t=this!==e&&this._intersectsWith(this.containerCache)&&b.contains(e.element[0],this.element[0])?!1:t})),t?(e.isOver||(e.isOver=1,o._parent=i.helper.parent(),e.currentItem=i.helper.appendTo(e.element).data("ui-sortable-item",!0),e.options._helper=e.options.helper,e.options.helper=function(){return i.helper[0]},s.target=e.currentItem[0],e._mouseCapture(s,!0),e._mouseStart(s,!0,!0),e.offset.click.top=o.offset.click.top,e.offset.click.left=o.offset.click.left,e.offset.parent.left-=o.offset.parent.left-e.offset.parent.left,e.offset.parent.top-=o.offset.parent.top-e.offset.parent.top,o._trigger("toSortable",s),o.dropped=e.element,b.each(o.sortables,function(){this.refreshPositions()}),o.currentItem=o.element,e.fromOutside=o),e.currentItem&&(e._mouseDrag(s),i.position=e.position)):e.isOver&&(e.isOver=0,e.cancelHelperRemoval=!0,e.options._revert=e.options.revert,e.options.revert=!1,e._trigger("out",s,e._uiHash(e)),e._mouseStop(s,!0),e.options.revert=e.options._revert,e.options.helper=e.options._helper,e.placeholder&&e.placeholder.remove(),i.helper.appendTo(o._parent),o._refreshOffsets(s),i.position=o._generatePosition(s,!0),o._trigger("fromSortable",s),o.dropped=!1,b.each(o.sortables,function(){this.refreshPositions()}))})}}),b.ui.plugin.add("draggable","cursor",{start:function(t,e,s){var i=b("body"),s=s.options;i.css("cursor")&&(s._cursor=i.css("cursor")),i.css("cursor",s.cursor)},stop:function(t,e,s){s=s.options;s._cursor&&b("body").css("cursor",s._cursor)}}),b.ui.plugin.add("draggable","opacity",{start:function(t,e,s){e=b(e.helper),s=s.options;e.css("opacity")&&(s._opacity=e.css("opacity")),e.css("opacity",s.opacity)},stop:function(t,e,s){s=s.options;s._opacity&&b(e.helper).css("opacity",s._opacity)}}),b.ui.plugin.add("draggable","scroll",{start:function(t,e,s){s.scrollParentNotHidden||(s.scrollParentNotHidden=s.helper.scrollParent(!1)),s.scrollParentNotHidden[0]!==s.document[0]&&"HTML"!==s.scrollParentNotHidden[0].tagName&&(s.overflowOffset=s.scrollParentNotHidden.offset())},drag:function(t,e,s){var i=s.options,o=!1,n=s.scrollParentNotHidden[0],r=s.document[0];n!==r&&"HTML"!==n.tagName?(i.axis&&"x"===i.axis||(s.overflowOffset.top+n.offsetHeight-t.pageY1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery); + +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +// This is CodeMirror (https://codemirror.net), a code editor +// implemented in JavaScript on top of the browser's DOM. +// +// You can find some technical background for some of the code below +// at http://marijnhaverbeke.nl/blog/#cm-internals . + +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.CodeMirror = factory()); +}(this, (function () { 'use strict'; + + // Kludges for bugs and behavior differences that can't be feature + // detected are enabled based on userAgent etc sniffing. + var userAgent = navigator.userAgent; + var platform = navigator.platform; + + var gecko = /gecko\/\d/i.test(userAgent); + var ie_upto10 = /MSIE \d/.test(userAgent); + var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(userAgent); + var edge = /Edge\/(\d+)/.exec(userAgent); + var ie = ie_upto10 || ie_11up || edge; + var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : +(edge || ie_11up)[1]); + var webkit = !edge && /WebKit\//.test(userAgent); + var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent); + var chrome = !edge && /Chrome\//.test(userAgent); + var presto = /Opera\//.test(userAgent); + var safari = /Apple Computer/.test(navigator.vendor); + var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent); + var phantom = /PhantomJS/.test(userAgent); + + var ios = !edge && /AppleWebKit/.test(userAgent) && /Mobile\/\w+/.test(userAgent); + var android = /Android/.test(userAgent); + // This is woefully incomplete. Suggestions for alternative methods welcome. + var mobile = ios || android || /webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(userAgent); + var mac = ios || /Mac/.test(platform); + var chromeOS = /\bCrOS\b/.test(userAgent); + var windows = /win/i.test(platform); + + var presto_version = presto && userAgent.match(/Version\/(\d*\.\d*)/); + if (presto_version) { presto_version = Number(presto_version[1]); } + if (presto_version && presto_version >= 15) { presto = false; webkit = true; } + // Some browsers use the wrong event properties to signal cmd/ctrl on OS X + var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11)); + var captureRightClick = gecko || (ie && ie_version >= 9); + + function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*") } + + var rmClass = function(node, cls) { + var current = node.className; + var match = classTest(cls).exec(current); + if (match) { + var after = current.slice(match.index + match[0].length); + node.className = current.slice(0, match.index) + (after ? match[1] + after : ""); + } + }; + + function removeChildren(e) { + for (var count = e.childNodes.length; count > 0; --count) + { e.removeChild(e.firstChild); } + return e + } + + function removeChildrenAndAdd(parent, e) { + return removeChildren(parent).appendChild(e) + } + + function elt(tag, content, className, style) { + var e = document.createElement(tag); + if (className) { e.className = className; } + if (style) { e.style.cssText = style; } + if (typeof content == "string") { e.appendChild(document.createTextNode(content)); } + else if (content) { for (var i = 0; i < content.length; ++i) { e.appendChild(content[i]); } } + return e + } + // wrapper for elt, which removes the elt from the accessibility tree + function eltP(tag, content, className, style) { + var e = elt(tag, content, className, style); + e.setAttribute("role", "presentation"); + return e + } + + var range; + if (document.createRange) { range = function(node, start, end, endNode) { + var r = document.createRange(); + r.setEnd(endNode || node, end); + r.setStart(node, start); + return r + }; } + else { range = function(node, start, end) { + var r = document.body.createTextRange(); + try { r.moveToElementText(node.parentNode); } + catch(e) { return r } + r.collapse(true); + r.moveEnd("character", end); + r.moveStart("character", start); + return r + }; } + + function contains(parent, child) { + if (child.nodeType == 3) // Android browser always returns false when child is a textnode + { child = child.parentNode; } + if (parent.contains) + { return parent.contains(child) } + do { + if (child.nodeType == 11) { child = child.host; } + if (child == parent) { return true } + } while (child = child.parentNode) + } + + function activeElt() { + // IE and Edge may throw an "Unspecified Error" when accessing document.activeElement. + // IE < 10 will throw when accessed while the page is loading or in an iframe. + // IE > 9 and Edge will throw when accessed in an iframe if document.body is unavailable. + var activeElement; + try { + activeElement = document.activeElement; + } catch(e) { + activeElement = document.body || null; + } + while (activeElement && activeElement.shadowRoot && activeElement.shadowRoot.activeElement) + { activeElement = activeElement.shadowRoot.activeElement; } + return activeElement + } + + function addClass(node, cls) { + var current = node.className; + if (!classTest(cls).test(current)) { node.className += (current ? " " : "") + cls; } + } + function joinClasses(a, b) { + var as = a.split(" "); + for (var i = 0; i < as.length; i++) + { if (as[i] && !classTest(as[i]).test(b)) { b += " " + as[i]; } } + return b + } + + var selectInput = function(node) { node.select(); }; + if (ios) // Mobile Safari apparently has a bug where select() is broken. + { selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length; }; } + else if (ie) // Suppress mysterious IE10 errors + { selectInput = function(node) { try { node.select(); } catch(_e) {} }; } + + function bind(f) { + var args = Array.prototype.slice.call(arguments, 1); + return function(){return f.apply(null, args)} + } + + function copyObj(obj, target, overwrite) { + if (!target) { target = {}; } + for (var prop in obj) + { if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop))) + { target[prop] = obj[prop]; } } + return target + } + + // Counts the column offset in a string, taking tabs into account. + // Used mostly to find indentation. + function countColumn(string, end, tabSize, startIndex, startValue) { + if (end == null) { + end = string.search(/[^\s\u00a0]/); + if (end == -1) { end = string.length; } + } + for (var i = startIndex || 0, n = startValue || 0;;) { + var nextTab = string.indexOf("\t", i); + if (nextTab < 0 || nextTab >= end) + { return n + (end - i) } + n += nextTab - i; + n += tabSize - (n % tabSize); + i = nextTab + 1; + } + } + + var Delayed = function() {this.id = null;}; + Delayed.prototype.set = function (ms, f) { + clearTimeout(this.id); + this.id = setTimeout(f, ms); + }; + + function indexOf(array, elt) { + for (var i = 0; i < array.length; ++i) + { if (array[i] == elt) { return i } } + return -1 + } + + // Number of pixels added to scroller and sizer to hide scrollbar + var scrollerGap = 30; + + // Returned or thrown by various protocols to signal 'I'm not + // handling this'. + var Pass = {toString: function(){return "CodeMirror.Pass"}}; + + // Reused option objects for setSelection & friends + var sel_dontScroll = {scroll: false}, sel_mouse = {origin: "*mouse"}, sel_move = {origin: "+move"}; + + // The inverse of countColumn -- find the offset that corresponds to + // a particular column. + function findColumn(string, goal, tabSize) { + for (var pos = 0, col = 0;;) { + var nextTab = string.indexOf("\t", pos); + if (nextTab == -1) { nextTab = string.length; } + var skipped = nextTab - pos; + if (nextTab == string.length || col + skipped >= goal) + { return pos + Math.min(skipped, goal - col) } + col += nextTab - pos; + col += tabSize - (col % tabSize); + pos = nextTab + 1; + if (col >= goal) { return pos } + } + } + + var spaceStrs = [""]; + function spaceStr(n) { + while (spaceStrs.length <= n) + { spaceStrs.push(lst(spaceStrs) + " "); } + return spaceStrs[n] + } + + function lst(arr) { return arr[arr.length-1] } + + function map(array, f) { + var out = []; + for (var i = 0; i < array.length; i++) { out[i] = f(array[i], i); } + return out + } + + function insertSorted(array, value, score) { + var pos = 0, priority = score(value); + while (pos < array.length && score(array[pos]) <= priority) { pos++; } + array.splice(pos, 0, value); + } + + function nothing() {} + + function createObj(base, props) { + var inst; + if (Object.create) { + inst = Object.create(base); + } else { + nothing.prototype = base; + inst = new nothing(); + } + if (props) { copyObj(props, inst); } + return inst + } + + var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; + function isWordCharBasic(ch) { + return /\w/.test(ch) || ch > "\x80" && + (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)) + } + function isWordChar(ch, helper) { + if (!helper) { return isWordCharBasic(ch) } + if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) { return true } + return helper.test(ch) + } + + function isEmpty(obj) { + for (var n in obj) { if (obj.hasOwnProperty(n) && obj[n]) { return false } } + return true + } + + // Extending unicode characters. A series of a non-extending char + + // any number of extending chars is treated as a single unit as far + // as editing and measuring is concerned. This is not fully correct, + // since some scripts/fonts/browsers also treat other configurations + // of code points as a group. + var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/; + function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch) } + + // Returns a number from the range [`0`; `str.length`] unless `pos` is outside that range. + function skipExtendingChars(str, pos, dir) { + while ((dir < 0 ? pos > 0 : pos < str.length) && isExtendingChar(str.charAt(pos))) { pos += dir; } + return pos + } + + // Returns the value from the range [`from`; `to`] that satisfies + // `pred` and is closest to `from`. Assumes that at least `to` + // satisfies `pred`. Supports `from` being greater than `to`. + function findFirst(pred, from, to) { + // At any point we are certain `to` satisfies `pred`, don't know + // whether `from` does. + var dir = from > to ? -1 : 1; + for (;;) { + if (from == to) { return from } + var midF = (from + to) / 2, mid = dir < 0 ? Math.ceil(midF) : Math.floor(midF); + if (mid == from) { return pred(mid) ? from : to } + if (pred(mid)) { to = mid; } + else { from = mid + dir; } + } + } + + // The display handles the DOM integration, both for input reading + // and content drawing. It holds references to DOM nodes and + // display-related state. + + function Display(place, doc, input) { + var d = this; + this.input = input; + + // Covers bottom-right square when both scrollbars are present. + d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler"); + d.scrollbarFiller.setAttribute("cm-not-content", "true"); + // Covers bottom of gutter when coverGutterNextToScrollbar is on + // and h scrollbar is present. + d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler"); + d.gutterFiller.setAttribute("cm-not-content", "true"); + // Will contain the actual code, positioned to cover the viewport. + d.lineDiv = eltP("div", null, "CodeMirror-code"); + // Elements are added to these to represent selection and cursors. + d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1"); + d.cursorDiv = elt("div", null, "CodeMirror-cursors"); + // A visibility: hidden element used to find the size of things. + d.measure = elt("div", null, "CodeMirror-measure"); + // When lines outside of the viewport are measured, they are drawn in this. + d.lineMeasure = elt("div", null, "CodeMirror-measure"); + // Wraps everything that needs to exist inside the vertically-padded coordinate system + d.lineSpace = eltP("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv], + null, "position: relative; outline: none"); + var lines = eltP("div", [d.lineSpace], "CodeMirror-lines"); + // Moved around its parent to cover visible view. + d.mover = elt("div", [lines], null, "position: relative"); + // Set to the height of the document, allowing scrolling. + d.sizer = elt("div", [d.mover], "CodeMirror-sizer"); + d.sizerWidth = null; + // Behavior of elts with overflow: auto and padding is + // inconsistent across browsers. This is used to ensure the + // scrollable area is big enough. + d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerGap + "px; width: 1px;"); + // Will contain the gutters, if any. + d.gutters = elt("div", null, "CodeMirror-gutters"); + d.lineGutter = null; + // Actual scrollable element. + d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll"); + d.scroller.setAttribute("tabIndex", "-1"); + // The element in which the editor lives. + d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror"); + + // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported) + if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; } + if (!webkit && !(gecko && mobile)) { d.scroller.draggable = true; } + + if (place) { + if (place.appendChild) { place.appendChild(d.wrapper); } + else { place(d.wrapper); } + } + + // Current rendered range (may be bigger than the view window). + d.viewFrom = d.viewTo = doc.first; + d.reportedViewFrom = d.reportedViewTo = doc.first; + // Information about the rendered lines. + d.view = []; + d.renderedView = null; + // Holds info about a single rendered line when it was rendered + // for measurement, while not in view. + d.externalMeasured = null; + // Empty space (in pixels) above the view + d.viewOffset = 0; + d.lastWrapHeight = d.lastWrapWidth = 0; + d.updateLineNumbers = null; + + d.nativeBarWidth = d.barHeight = d.barWidth = 0; + d.scrollbarsClipped = false; + + // Used to only resize the line number gutter when necessary (when + // the amount of lines crosses a boundary that makes its width change) + d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null; + // Set to true when a non-horizontal-scrolling line widget is + // added. As an optimization, line widget aligning is skipped when + // this is false. + d.alignWidgets = false; + + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; + + // Tracks the maximum line length so that the horizontal scrollbar + // can be kept static when scrolling. + d.maxLine = null; + d.maxLineLength = 0; + d.maxLineChanged = false; + + // Used for measuring wheel scrolling granularity + d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null; + + // True when shift is held down. + d.shift = false; + + // Used to track whether anything happened since the context menu + // was opened. + d.selForContextMenu = null; + + d.activeTouch = null; + + input.init(d); + } + + // Find the line object corresponding to the given line number. + function getLine(doc, n) { + n -= doc.first; + if (n < 0 || n >= doc.size) { throw new Error("There is no line " + (n + doc.first) + " in the document.") } + var chunk = doc; + while (!chunk.lines) { + for (var i = 0;; ++i) { + var child = chunk.children[i], sz = child.chunkSize(); + if (n < sz) { chunk = child; break } + n -= sz; + } + } + return chunk.lines[n] + } + + // Get the part of a document between two positions, as an array of + // strings. + function getBetween(doc, start, end) { + var out = [], n = start.line; + doc.iter(start.line, end.line + 1, function (line) { + var text = line.text; + if (n == end.line) { text = text.slice(0, end.ch); } + if (n == start.line) { text = text.slice(start.ch); } + out.push(text); + ++n; + }); + return out + } + // Get the lines between from and to, as array of strings. + function getLines(doc, from, to) { + var out = []; + doc.iter(from, to, function (line) { out.push(line.text); }); // iter aborts when callback returns truthy value + return out + } + + // Update the height of a line, propagating the height change + // upwards to parent nodes. + function updateLineHeight(line, height) { + var diff = height - line.height; + if (diff) { for (var n = line; n; n = n.parent) { n.height += diff; } } + } + + // Given a line object, find its line number by walking up through + // its parent links. + function lineNo(line) { + if (line.parent == null) { return null } + var cur = line.parent, no = indexOf(cur.lines, line); + for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { + for (var i = 0;; ++i) { + if (chunk.children[i] == cur) { break } + no += chunk.children[i].chunkSize(); + } + } + return no + cur.first + } + + // Find the line at the given vertical position, using the height + // information in the document tree. + function lineAtHeight(chunk, h) { + var n = chunk.first; + outer: do { + for (var i$1 = 0; i$1 < chunk.children.length; ++i$1) { + var child = chunk.children[i$1], ch = child.height; + if (h < ch) { chunk = child; continue outer } + h -= ch; + n += child.chunkSize(); + } + return n + } while (!chunk.lines) + var i = 0; + for (; i < chunk.lines.length; ++i) { + var line = chunk.lines[i], lh = line.height; + if (h < lh) { break } + h -= lh; + } + return n + i + } + + function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size} + + function lineNumberFor(options, i) { + return String(options.lineNumberFormatter(i + options.firstLineNumber)) + } + + // A Pos instance represents a position within the text. + function Pos(line, ch, sticky) { + if ( sticky === void 0 ) sticky = null; + + if (!(this instanceof Pos)) { return new Pos(line, ch, sticky) } + this.line = line; + this.ch = ch; + this.sticky = sticky; + } + + // Compare two positions, return 0 if they are the same, a negative + // number when a is less, and a positive number otherwise. + function cmp(a, b) { return a.line - b.line || a.ch - b.ch } + + function equalCursorPos(a, b) { return a.sticky == b.sticky && cmp(a, b) == 0 } + + function copyPos(x) {return Pos(x.line, x.ch)} + function maxPos(a, b) { return cmp(a, b) < 0 ? b : a } + function minPos(a, b) { return cmp(a, b) < 0 ? a : b } + + // Most of the external API clips given positions to make sure they + // actually exist within the document. + function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1))} + function clipPos(doc, pos) { + if (pos.line < doc.first) { return Pos(doc.first, 0) } + var last = doc.first + doc.size - 1; + if (pos.line > last) { return Pos(last, getLine(doc, last).text.length) } + return clipToLen(pos, getLine(doc, pos.line).text.length) + } + function clipToLen(pos, linelen) { + var ch = pos.ch; + if (ch == null || ch > linelen) { return Pos(pos.line, linelen) } + else if (ch < 0) { return Pos(pos.line, 0) } + else { return pos } + } + function clipPosArray(doc, array) { + var out = []; + for (var i = 0; i < array.length; i++) { out[i] = clipPos(doc, array[i]); } + return out + } + + // Optimize some code when these features are not used. + var sawReadOnlySpans = false, sawCollapsedSpans = false; + + function seeReadOnlySpans() { + sawReadOnlySpans = true; + } + + function seeCollapsedSpans() { + sawCollapsedSpans = true; + } + + // TEXTMARKER SPANS + + function MarkedSpan(marker, from, to) { + this.marker = marker; + this.from = from; this.to = to; + } + + // Search an array of spans for a span matching the given marker. + function getMarkedSpanFor(spans, marker) { + if (spans) { for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if (span.marker == marker) { return span } + } } + } + // Remove a span from an array, returning undefined if no spans are + // left (we don't store arrays for lines without spans). + function removeMarkedSpan(spans, span) { + var r; + for (var i = 0; i < spans.length; ++i) + { if (spans[i] != span) { (r || (r = [])).push(spans[i]); } } + return r + } + // Add a span to a line. + function addMarkedSpan(line, span) { + line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span]; + span.marker.attachLine(line); + } + + // Used for the algorithm that adjusts markers for a change in the + // document. These functions cut an array of spans at a given + // character position, returning an array of remaining chunks (or + // undefined if nothing remains). + function markedSpansBefore(old, startCh, isInsert) { + var nw; + if (old) { for (var i = 0; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); + if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) { + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh) + ;(nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to)); + } + } } + return nw + } + function markedSpansAfter(old, endCh, isInsert) { + var nw; + if (old) { for (var i = 0; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh); + if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) { + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh) + ;(nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh, + span.to == null ? null : span.to - endCh)); + } + } } + return nw + } + + // Given a change object, compute the new set of marker spans that + // cover the line in which the change took place. Removes spans + // entirely within the change, reconnects spans belonging to the + // same marker that appear on both sides of the change, and cuts off + // spans partially within the change. Returns an array of span + // arrays with one element for each line in (after) the change. + function stretchSpansOverChange(doc, change) { + if (change.full) { return null } + var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans; + var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans; + if (!oldFirst && !oldLast) { return null } + + var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0; + // Get the spans that 'stick out' on both sides + var first = markedSpansBefore(oldFirst, startCh, isInsert); + var last = markedSpansAfter(oldLast, endCh, isInsert); + + // Next, merge those two ends + var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0); + if (first) { + // Fix up .to properties of first + for (var i = 0; i < first.length; ++i) { + var span = first[i]; + if (span.to == null) { + var found = getMarkedSpanFor(last, span.marker); + if (!found) { span.to = startCh; } + else if (sameLine) { span.to = found.to == null ? null : found.to + offset; } + } + } + } + if (last) { + // Fix up .from in last (or move them into first in case of sameLine) + for (var i$1 = 0; i$1 < last.length; ++i$1) { + var span$1 = last[i$1]; + if (span$1.to != null) { span$1.to += offset; } + if (span$1.from == null) { + var found$1 = getMarkedSpanFor(first, span$1.marker); + if (!found$1) { + span$1.from = offset; + if (sameLine) { (first || (first = [])).push(span$1); } + } + } else { + span$1.from += offset; + if (sameLine) { (first || (first = [])).push(span$1); } + } + } + } + // Make sure we didn't create any zero-length spans + if (first) { first = clearEmptySpans(first); } + if (last && last != first) { last = clearEmptySpans(last); } + + var newMarkers = [first]; + if (!sameLine) { + // Fill gap with whole-line-spans + var gap = change.text.length - 2, gapMarkers; + if (gap > 0 && first) + { for (var i$2 = 0; i$2 < first.length; ++i$2) + { if (first[i$2].to == null) + { (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i$2].marker, null, null)); } } } + for (var i$3 = 0; i$3 < gap; ++i$3) + { newMarkers.push(gapMarkers); } + newMarkers.push(last); + } + return newMarkers + } + + // Remove spans that are empty and don't have a clearWhenEmpty + // option of false. + function clearEmptySpans(spans) { + for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false) + { spans.splice(i--, 1); } + } + if (!spans.length) { return null } + return spans + } + + // Used to 'clip' out readOnly ranges when making a change. + function removeReadOnlyRanges(doc, from, to) { + var markers = null; + doc.iter(from.line, to.line + 1, function (line) { + if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) { + var mark = line.markedSpans[i].marker; + if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) + { (markers || (markers = [])).push(mark); } + } } + }); + if (!markers) { return null } + var parts = [{from: from, to: to}]; + for (var i = 0; i < markers.length; ++i) { + var mk = markers[i], m = mk.find(0); + for (var j = 0; j < parts.length; ++j) { + var p = parts[j]; + if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) { continue } + var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to); + if (dfrom < 0 || !mk.inclusiveLeft && !dfrom) + { newParts.push({from: p.from, to: m.from}); } + if (dto > 0 || !mk.inclusiveRight && !dto) + { newParts.push({from: m.to, to: p.to}); } + parts.splice.apply(parts, newParts); + j += newParts.length - 3; + } + } + return parts + } + + // Connect or disconnect spans from a line. + function detachMarkedSpans(line) { + var spans = line.markedSpans; + if (!spans) { return } + for (var i = 0; i < spans.length; ++i) + { spans[i].marker.detachLine(line); } + line.markedSpans = null; + } + function attachMarkedSpans(line, spans) { + if (!spans) { return } + for (var i = 0; i < spans.length; ++i) + { spans[i].marker.attachLine(line); } + line.markedSpans = spans; + } + + // Helpers used when computing which overlapping collapsed span + // counts as the larger one. + function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0 } + function extraRight(marker) { return marker.inclusiveRight ? 1 : 0 } + + // Returns a number indicating which of two overlapping collapsed + // spans is larger (and thus includes the other). Falls back to + // comparing ids when the spans cover exactly the same range. + function compareCollapsedMarkers(a, b) { + var lenDiff = a.lines.length - b.lines.length; + if (lenDiff != 0) { return lenDiff } + var aPos = a.find(), bPos = b.find(); + var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b); + if (fromCmp) { return -fromCmp } + var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b); + if (toCmp) { return toCmp } + return b.id - a.id + } + + // Find out whether a line ends or starts in a collapsed span. If + // so, return the marker for that span. + function collapsedSpanAtSide(line, start) { + var sps = sawCollapsedSpans && line.markedSpans, found; + if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) { + sp = sps[i]; + if (sp.marker.collapsed && (start ? sp.from : sp.to) == null && + (!found || compareCollapsedMarkers(found, sp.marker) < 0)) + { found = sp.marker; } + } } + return found + } + function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true) } + function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false) } + + function collapsedSpanAround(line, ch) { + var sps = sawCollapsedSpans && line.markedSpans, found; + if (sps) { for (var i = 0; i < sps.length; ++i) { + var sp = sps[i]; + if (sp.marker.collapsed && (sp.from == null || sp.from < ch) && (sp.to == null || sp.to > ch) && + (!found || compareCollapsedMarkers(found, sp.marker) < 0)) { found = sp.marker; } + } } + return found + } + + // Test whether there exists a collapsed span that partially + // overlaps (covers the start or end, but not both) of a new span. + // Such overlap is not allowed. + function conflictingCollapsedRange(doc, lineNo$$1, from, to, marker) { + var line = getLine(doc, lineNo$$1); + var sps = sawCollapsedSpans && line.markedSpans; + if (sps) { for (var i = 0; i < sps.length; ++i) { + var sp = sps[i]; + if (!sp.marker.collapsed) { continue } + var found = sp.marker.find(0); + var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker); + var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker); + if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) { continue } + if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.to, from) >= 0 : cmp(found.to, from) > 0) || + fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.from, to) <= 0 : cmp(found.from, to) < 0)) + { return true } + } } + } + + // A visual line is a line as drawn on the screen. Folding, for + // example, can cause multiple logical lines to appear on the same + // visual line. This finds the start of the visual line that the + // given line is part of (usually that is the line itself). + function visualLine(line) { + var merged; + while (merged = collapsedSpanAtStart(line)) + { line = merged.find(-1, true).line; } + return line + } + + function visualLineEnd(line) { + var merged; + while (merged = collapsedSpanAtEnd(line)) + { line = merged.find(1, true).line; } + return line + } + + // Returns an array of logical lines that continue the visual line + // started by the argument, or undefined if there are no such lines. + function visualLineContinued(line) { + var merged, lines; + while (merged = collapsedSpanAtEnd(line)) { + line = merged.find(1, true).line + ;(lines || (lines = [])).push(line); + } + return lines + } + + // Get the line number of the start of the visual line that the + // given line number is part of. + function visualLineNo(doc, lineN) { + var line = getLine(doc, lineN), vis = visualLine(line); + if (line == vis) { return lineN } + return lineNo(vis) + } + + // Get the line number of the start of the next visual line after + // the given line. + function visualLineEndNo(doc, lineN) { + if (lineN > doc.lastLine()) { return lineN } + var line = getLine(doc, lineN), merged; + if (!lineIsHidden(doc, line)) { return lineN } + while (merged = collapsedSpanAtEnd(line)) + { line = merged.find(1, true).line; } + return lineNo(line) + 1 + } + + // Compute whether a line is hidden. Lines count as hidden when they + // are part of a visual line that starts with another line, or when + // they are entirely covered by collapsed, non-widget span. + function lineIsHidden(doc, line) { + var sps = sawCollapsedSpans && line.markedSpans; + if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) { + sp = sps[i]; + if (!sp.marker.collapsed) { continue } + if (sp.from == null) { return true } + if (sp.marker.widgetNode) { continue } + if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) + { return true } + } } + } + function lineIsHiddenInner(doc, line, span) { + if (span.to == null) { + var end = span.marker.find(1, true); + return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker)) + } + if (span.marker.inclusiveRight && span.to == line.text.length) + { return true } + for (var sp = (void 0), i = 0; i < line.markedSpans.length; ++i) { + sp = line.markedSpans[i]; + if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to && + (sp.to == null || sp.to != span.from) && + (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && + lineIsHiddenInner(doc, line, sp)) { return true } + } + } + + // Find the height above the given line. + function heightAtLine(lineObj) { + lineObj = visualLine(lineObj); + + var h = 0, chunk = lineObj.parent; + for (var i = 0; i < chunk.lines.length; ++i) { + var line = chunk.lines[i]; + if (line == lineObj) { break } + else { h += line.height; } + } + for (var p = chunk.parent; p; chunk = p, p = chunk.parent) { + for (var i$1 = 0; i$1 < p.children.length; ++i$1) { + var cur = p.children[i$1]; + if (cur == chunk) { break } + else { h += cur.height; } + } + } + return h + } + + // Compute the character length of a line, taking into account + // collapsed ranges (see markText) that might hide parts, and join + // other lines onto it. + function lineLength(line) { + if (line.height == 0) { return 0 } + var len = line.text.length, merged, cur = line; + while (merged = collapsedSpanAtStart(cur)) { + var found = merged.find(0, true); + cur = found.from.line; + len += found.from.ch - found.to.ch; + } + cur = line; + while (merged = collapsedSpanAtEnd(cur)) { + var found$1 = merged.find(0, true); + len -= cur.text.length - found$1.from.ch; + cur = found$1.to.line; + len += cur.text.length - found$1.to.ch; + } + return len + } + + // Find the longest line in the document. + function findMaxLine(cm) { + var d = cm.display, doc = cm.doc; + d.maxLine = getLine(doc, doc.first); + d.maxLineLength = lineLength(d.maxLine); + d.maxLineChanged = true; + doc.iter(function (line) { + var len = lineLength(line); + if (len > d.maxLineLength) { + d.maxLineLength = len; + d.maxLine = line; + } + }); + } + + // BIDI HELPERS + + function iterateBidiSections(order, from, to, f) { + if (!order) { return f(from, to, "ltr", 0) } + var found = false; + for (var i = 0; i < order.length; ++i) { + var part = order[i]; + if (part.from < to && part.to > from || from == to && part.to == from) { + f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr", i); + found = true; + } + } + if (!found) { f(from, to, "ltr"); } + } + + var bidiOther = null; + function getBidiPartAt(order, ch, sticky) { + var found; + bidiOther = null; + for (var i = 0; i < order.length; ++i) { + var cur = order[i]; + if (cur.from < ch && cur.to > ch) { return i } + if (cur.to == ch) { + if (cur.from != cur.to && sticky == "before") { found = i; } + else { bidiOther = i; } + } + if (cur.from == ch) { + if (cur.from != cur.to && sticky != "before") { found = i; } + else { bidiOther = i; } + } + } + return found != null ? found : bidiOther + } + + // Bidirectional ordering algorithm + // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm + // that this (partially) implements. + + // One-char codes used for character types: + // L (L): Left-to-Right + // R (R): Right-to-Left + // r (AL): Right-to-Left Arabic + // 1 (EN): European Number + // + (ES): European Number Separator + // % (ET): European Number Terminator + // n (AN): Arabic Number + // , (CS): Common Number Separator + // m (NSM): Non-Spacing Mark + // b (BN): Boundary Neutral + // s (B): Paragraph Separator + // t (S): Segment Separator + // w (WS): Whitespace + // N (ON): Other Neutrals + + // Returns null if characters are ordered as they appear + // (left-to-right), or an array of sections ({from, to, level} + // objects) in the order in which they occur visually. + var bidiOrdering = (function() { + // Character types for codepoints 0 to 0xff + var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN"; + // Character types for codepoints 0x600 to 0x6f9 + var arabicTypes = "nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmnNmmmmmmrrmmNmmmmrr1111111111"; + function charType(code) { + if (code <= 0xf7) { return lowTypes.charAt(code) } + else if (0x590 <= code && code <= 0x5f4) { return "R" } + else if (0x600 <= code && code <= 0x6f9) { return arabicTypes.charAt(code - 0x600) } + else if (0x6ee <= code && code <= 0x8ac) { return "r" } + else if (0x2000 <= code && code <= 0x200b) { return "w" } + else if (code == 0x200c) { return "b" } + else { return "L" } + } + + var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/; + var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/; + + function BidiSpan(level, from, to) { + this.level = level; + this.from = from; this.to = to; + } + + return function(str, direction) { + var outerType = direction == "ltr" ? "L" : "R"; + + if (str.length == 0 || direction == "ltr" && !bidiRE.test(str)) { return false } + var len = str.length, types = []; + for (var i = 0; i < len; ++i) + { types.push(charType(str.charCodeAt(i))); } + + // W1. Examine each non-spacing mark (NSM) in the level run, and + // change the type of the NSM to the type of the previous + // character. If the NSM is at the start of the level run, it will + // get the type of sor. + for (var i$1 = 0, prev = outerType; i$1 < len; ++i$1) { + var type = types[i$1]; + if (type == "m") { types[i$1] = prev; } + else { prev = type; } + } + + // W2. Search backwards from each instance of a European number + // until the first strong type (R, L, AL, or sor) is found. If an + // AL is found, change the type of the European number to Arabic + // number. + // W3. Change all ALs to R. + for (var i$2 = 0, cur = outerType; i$2 < len; ++i$2) { + var type$1 = types[i$2]; + if (type$1 == "1" && cur == "r") { types[i$2] = "n"; } + else if (isStrong.test(type$1)) { cur = type$1; if (type$1 == "r") { types[i$2] = "R"; } } + } + + // W4. A single European separator between two European numbers + // changes to a European number. A single common separator between + // two numbers of the same type changes to that type. + for (var i$3 = 1, prev$1 = types[0]; i$3 < len - 1; ++i$3) { + var type$2 = types[i$3]; + if (type$2 == "+" && prev$1 == "1" && types[i$3+1] == "1") { types[i$3] = "1"; } + else if (type$2 == "," && prev$1 == types[i$3+1] && + (prev$1 == "1" || prev$1 == "n")) { types[i$3] = prev$1; } + prev$1 = type$2; + } + + // W5. A sequence of European terminators adjacent to European + // numbers changes to all European numbers. + // W6. Otherwise, separators and terminators change to Other + // Neutral. + for (var i$4 = 0; i$4 < len; ++i$4) { + var type$3 = types[i$4]; + if (type$3 == ",") { types[i$4] = "N"; } + else if (type$3 == "%") { + var end = (void 0); + for (end = i$4 + 1; end < len && types[end] == "%"; ++end) {} + var replace = (i$4 && types[i$4-1] == "!") || (end < len && types[end] == "1") ? "1" : "N"; + for (var j = i$4; j < end; ++j) { types[j] = replace; } + i$4 = end - 1; + } + } + + // W7. Search backwards from each instance of a European number + // until the first strong type (R, L, or sor) is found. If an L is + // found, then change the type of the European number to L. + for (var i$5 = 0, cur$1 = outerType; i$5 < len; ++i$5) { + var type$4 = types[i$5]; + if (cur$1 == "L" && type$4 == "1") { types[i$5] = "L"; } + else if (isStrong.test(type$4)) { cur$1 = type$4; } + } + + // N1. A sequence of neutrals takes the direction of the + // surrounding strong text if the text on both sides has the same + // direction. European and Arabic numbers act as if they were R in + // terms of their influence on neutrals. Start-of-level-run (sor) + // and end-of-level-run (eor) are used at level run boundaries. + // N2. Any remaining neutrals take the embedding direction. + for (var i$6 = 0; i$6 < len; ++i$6) { + if (isNeutral.test(types[i$6])) { + var end$1 = (void 0); + for (end$1 = i$6 + 1; end$1 < len && isNeutral.test(types[end$1]); ++end$1) {} + var before = (i$6 ? types[i$6-1] : outerType) == "L"; + var after = (end$1 < len ? types[end$1] : outerType) == "L"; + var replace$1 = before == after ? (before ? "L" : "R") : outerType; + for (var j$1 = i$6; j$1 < end$1; ++j$1) { types[j$1] = replace$1; } + i$6 = end$1 - 1; + } + } + + // Here we depart from the documented algorithm, in order to avoid + // building up an actual levels array. Since there are only three + // levels (0, 1, 2) in an implementation that doesn't take + // explicit embedding into account, we can build up the order on + // the fly, without following the level-based algorithm. + var order = [], m; + for (var i$7 = 0; i$7 < len;) { + if (countsAsLeft.test(types[i$7])) { + var start = i$7; + for (++i$7; i$7 < len && countsAsLeft.test(types[i$7]); ++i$7) {} + order.push(new BidiSpan(0, start, i$7)); + } else { + var pos = i$7, at = order.length; + for (++i$7; i$7 < len && types[i$7] != "L"; ++i$7) {} + for (var j$2 = pos; j$2 < i$7;) { + if (countsAsNum.test(types[j$2])) { + if (pos < j$2) { order.splice(at, 0, new BidiSpan(1, pos, j$2)); } + var nstart = j$2; + for (++j$2; j$2 < i$7 && countsAsNum.test(types[j$2]); ++j$2) {} + order.splice(at, 0, new BidiSpan(2, nstart, j$2)); + pos = j$2; + } else { ++j$2; } + } + if (pos < i$7) { order.splice(at, 0, new BidiSpan(1, pos, i$7)); } + } + } + if (direction == "ltr") { + if (order[0].level == 1 && (m = str.match(/^\s+/))) { + order[0].from = m[0].length; + order.unshift(new BidiSpan(0, 0, m[0].length)); + } + if (lst(order).level == 1 && (m = str.match(/\s+$/))) { + lst(order).to -= m[0].length; + order.push(new BidiSpan(0, len - m[0].length, len)); + } + } + + return direction == "rtl" ? order.reverse() : order + } + })(); + + // Get the bidi ordering for the given line (and cache it). Returns + // false for lines that are fully left-to-right, and an array of + // BidiSpan objects otherwise. + function getOrder(line, direction) { + var order = line.order; + if (order == null) { order = line.order = bidiOrdering(line.text, direction); } + return order + } + + // EVENT HANDLING + + // Lightweight event framework. on/off also work on DOM nodes, + // registering native DOM handlers. + + var noHandlers = []; + + var on = function(emitter, type, f) { + if (emitter.addEventListener) { + emitter.addEventListener(type, f, false); + } else if (emitter.attachEvent) { + emitter.attachEvent("on" + type, f); + } else { + var map$$1 = emitter._handlers || (emitter._handlers = {}); + map$$1[type] = (map$$1[type] || noHandlers).concat(f); + } + }; + + function getHandlers(emitter, type) { + return emitter._handlers && emitter._handlers[type] || noHandlers + } + + function off(emitter, type, f) { + if (emitter.removeEventListener) { + emitter.removeEventListener(type, f, false); + } else if (emitter.detachEvent) { + emitter.detachEvent("on" + type, f); + } else { + var map$$1 = emitter._handlers, arr = map$$1 && map$$1[type]; + if (arr) { + var index = indexOf(arr, f); + if (index > -1) + { map$$1[type] = arr.slice(0, index).concat(arr.slice(index + 1)); } + } + } + } + + function signal(emitter, type /*, values...*/) { + var handlers = getHandlers(emitter, type); + if (!handlers.length) { return } + var args = Array.prototype.slice.call(arguments, 2); + for (var i = 0; i < handlers.length; ++i) { handlers[i].apply(null, args); } + } + + // The DOM events that CodeMirror handles can be overridden by + // registering a (non-DOM) handler on the editor for the event name, + // and preventDefault-ing the event in that handler. + function signalDOMEvent(cm, e, override) { + if (typeof e == "string") + { e = {type: e, preventDefault: function() { this.defaultPrevented = true; }}; } + signal(cm, override || e.type, cm, e); + return e_defaultPrevented(e) || e.codemirrorIgnore + } + + function signalCursorActivity(cm) { + var arr = cm._handlers && cm._handlers.cursorActivity; + if (!arr) { return } + var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []); + for (var i = 0; i < arr.length; ++i) { if (indexOf(set, arr[i]) == -1) + { set.push(arr[i]); } } + } + + function hasHandler(emitter, type) { + return getHandlers(emitter, type).length > 0 + } + + // Add on and off methods to a constructor's prototype, to make + // registering events on such objects more convenient. + function eventMixin(ctor) { + ctor.prototype.on = function(type, f) {on(this, type, f);}; + ctor.prototype.off = function(type, f) {off(this, type, f);}; + } + + // Due to the fact that we still support jurassic IE versions, some + // compatibility wrappers are needed. + + function e_preventDefault(e) { + if (e.preventDefault) { e.preventDefault(); } + else { e.returnValue = false; } + } + function e_stopPropagation(e) { + if (e.stopPropagation) { e.stopPropagation(); } + else { e.cancelBubble = true; } + } + function e_defaultPrevented(e) { + return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false + } + function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);} + + function e_target(e) {return e.target || e.srcElement} + function e_button(e) { + var b = e.which; + if (b == null) { + if (e.button & 1) { b = 1; } + else if (e.button & 2) { b = 3; } + else if (e.button & 4) { b = 2; } + } + if (mac && e.ctrlKey && b == 1) { b = 3; } + return b + } + + // Detect drag-and-drop + var dragAndDrop = function() { + // There is *some* kind of drag-and-drop support in IE6-8, but I + // couldn't get it to work yet. + if (ie && ie_version < 9) { return false } + var div = elt('div'); + return "draggable" in div || "dragDrop" in div + }(); + + var zwspSupported; + function zeroWidthElement(measure) { + if (zwspSupported == null) { + var test = elt("span", "\u200b"); + removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")])); + if (measure.firstChild.offsetHeight != 0) + { zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8); } + } + var node = zwspSupported ? elt("span", "\u200b") : + elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px"); + node.setAttribute("cm-text", ""); + return node + } + + // Feature-detect IE's crummy client rect reporting for bidi text + var badBidiRects; + function hasBadBidiRects(measure) { + if (badBidiRects != null) { return badBidiRects } + var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA")); + var r0 = range(txt, 0, 1).getBoundingClientRect(); + var r1 = range(txt, 1, 2).getBoundingClientRect(); + removeChildren(measure); + if (!r0 || r0.left == r0.right) { return false } // Safari returns null in some cases (#2780) + return badBidiRects = (r1.right - r0.right < 3) + } + + // See if "".split is the broken IE version, if so, provide an + // alternative way to split lines. + var splitLinesAuto = "\n\nb".split(/\n/).length != 3 ? function (string) { + var pos = 0, result = [], l = string.length; + while (pos <= l) { + var nl = string.indexOf("\n", pos); + if (nl == -1) { nl = string.length; } + var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); + var rt = line.indexOf("\r"); + if (rt != -1) { + result.push(line.slice(0, rt)); + pos += rt + 1; + } else { + result.push(line); + pos = nl + 1; + } + } + return result + } : function (string) { return string.split(/\r\n?|\n/); }; + + var hasSelection = window.getSelection ? function (te) { + try { return te.selectionStart != te.selectionEnd } + catch(e) { return false } + } : function (te) { + var range$$1; + try {range$$1 = te.ownerDocument.selection.createRange();} + catch(e) {} + if (!range$$1 || range$$1.parentElement() != te) { return false } + return range$$1.compareEndPoints("StartToEnd", range$$1) != 0 + }; + + var hasCopyEvent = (function () { + var e = elt("div"); + if ("oncopy" in e) { return true } + e.setAttribute("oncopy", "return;"); + return typeof e.oncopy == "function" + })(); + + var badZoomedRects = null; + function hasBadZoomedRects(measure) { + if (badZoomedRects != null) { return badZoomedRects } + var node = removeChildrenAndAdd(measure, elt("span", "x")); + var normal = node.getBoundingClientRect(); + var fromRange = range(node, 0, 1).getBoundingClientRect(); + return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1 + } + + // Known modes, by name and by MIME + var modes = {}, mimeModes = {}; + + // Extra arguments are stored as the mode's dependencies, which is + // used by (legacy) mechanisms like loadmode.js to automatically + // load a mode. (Preferred mechanism is the require/define calls.) + function defineMode(name, mode) { + if (arguments.length > 2) + { mode.dependencies = Array.prototype.slice.call(arguments, 2); } + modes[name] = mode; + } + + function defineMIME(mime, spec) { + mimeModes[mime] = spec; + } + + // Given a MIME type, a {name, ...options} config object, or a name + // string, return a mode config object. + function resolveMode(spec) { + if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { + spec = mimeModes[spec]; + } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { + var found = mimeModes[spec.name]; + if (typeof found == "string") { found = {name: found}; } + spec = createObj(found, spec); + spec.name = found.name; + } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { + return resolveMode("application/xml") + } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+json$/.test(spec)) { + return resolveMode("application/json") + } + if (typeof spec == "string") { return {name: spec} } + else { return spec || {name: "null"} } + } + + // Given a mode spec (anything that resolveMode accepts), find and + // initialize an actual mode object. + function getMode(options, spec) { + spec = resolveMode(spec); + var mfactory = modes[spec.name]; + if (!mfactory) { return getMode(options, "text/plain") } + var modeObj = mfactory(options, spec); + if (modeExtensions.hasOwnProperty(spec.name)) { + var exts = modeExtensions[spec.name]; + for (var prop in exts) { + if (!exts.hasOwnProperty(prop)) { continue } + if (modeObj.hasOwnProperty(prop)) { modeObj["_" + prop] = modeObj[prop]; } + modeObj[prop] = exts[prop]; + } + } + modeObj.name = spec.name; + if (spec.helperType) { modeObj.helperType = spec.helperType; } + if (spec.modeProps) { for (var prop$1 in spec.modeProps) + { modeObj[prop$1] = spec.modeProps[prop$1]; } } + + return modeObj + } + + // This can be used to attach properties to mode objects from + // outside the actual mode definition. + var modeExtensions = {}; + function extendMode(mode, properties) { + var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}); + copyObj(properties, exts); + } + + function copyState(mode, state) { + if (state === true) { return state } + if (mode.copyState) { return mode.copyState(state) } + var nstate = {}; + for (var n in state) { + var val = state[n]; + if (val instanceof Array) { val = val.concat([]); } + nstate[n] = val; + } + return nstate + } + + // Given a mode and a state (for that mode), find the inner mode and + // state at the position that the state refers to. + function innerMode(mode, state) { + var info; + while (mode.innerMode) { + info = mode.innerMode(state); + if (!info || info.mode == mode) { break } + state = info.state; + mode = info.mode; + } + return info || {mode: mode, state: state} + } + + function startState(mode, a1, a2) { + return mode.startState ? mode.startState(a1, a2) : true + } + + // STRING STREAM + + // Fed to the mode parsers, provides helper functions to make + // parsers more succinct. + + var StringStream = function(string, tabSize, lineOracle) { + this.pos = this.start = 0; + this.string = string; + this.tabSize = tabSize || 8; + this.lastColumnPos = this.lastColumnValue = 0; + this.lineStart = 0; + this.lineOracle = lineOracle; + }; + + StringStream.prototype.eol = function () {return this.pos >= this.string.length}; + StringStream.prototype.sol = function () {return this.pos == this.lineStart}; + StringStream.prototype.peek = function () {return this.string.charAt(this.pos) || undefined}; + StringStream.prototype.next = function () { + if (this.pos < this.string.length) + { return this.string.charAt(this.pos++) } + }; + StringStream.prototype.eat = function (match) { + var ch = this.string.charAt(this.pos); + var ok; + if (typeof match == "string") { ok = ch == match; } + else { ok = ch && (match.test ? match.test(ch) : match(ch)); } + if (ok) {++this.pos; return ch} + }; + StringStream.prototype.eatWhile = function (match) { + var start = this.pos; + while (this.eat(match)){} + return this.pos > start + }; + StringStream.prototype.eatSpace = function () { + var this$1 = this; + + var start = this.pos; + while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) { ++this$1.pos; } + return this.pos > start + }; + StringStream.prototype.skipToEnd = function () {this.pos = this.string.length;}; + StringStream.prototype.skipTo = function (ch) { + var found = this.string.indexOf(ch, this.pos); + if (found > -1) {this.pos = found; return true} + }; + StringStream.prototype.backUp = function (n) {this.pos -= n;}; + StringStream.prototype.column = function () { + if (this.lastColumnPos < this.start) { + this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue); + this.lastColumnPos = this.start; + } + return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0) + }; + StringStream.prototype.indentation = function () { + return countColumn(this.string, null, this.tabSize) - + (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0) + }; + StringStream.prototype.match = function (pattern, consume, caseInsensitive) { + if (typeof pattern == "string") { + var cased = function (str) { return caseInsensitive ? str.toLowerCase() : str; }; + var substr = this.string.substr(this.pos, pattern.length); + if (cased(substr) == cased(pattern)) { + if (consume !== false) { this.pos += pattern.length; } + return true + } + } else { + var match = this.string.slice(this.pos).match(pattern); + if (match && match.index > 0) { return null } + if (match && consume !== false) { this.pos += match[0].length; } + return match + } + }; + StringStream.prototype.current = function (){return this.string.slice(this.start, this.pos)}; + StringStream.prototype.hideFirstChars = function (n, inner) { + this.lineStart += n; + try { return inner() } + finally { this.lineStart -= n; } + }; + StringStream.prototype.lookAhead = function (n) { + var oracle = this.lineOracle; + return oracle && oracle.lookAhead(n) + }; + StringStream.prototype.baseToken = function () { + var oracle = this.lineOracle; + return oracle && oracle.baseToken(this.pos) + }; + + var SavedContext = function(state, lookAhead) { + this.state = state; + this.lookAhead = lookAhead; + }; + + var Context = function(doc, state, line, lookAhead) { + this.state = state; + this.doc = doc; + this.line = line; + this.maxLookAhead = lookAhead || 0; + this.baseTokens = null; + this.baseTokenPos = 1; + }; + + Context.prototype.lookAhead = function (n) { + var line = this.doc.getLine(this.line + n); + if (line != null && n > this.maxLookAhead) { this.maxLookAhead = n; } + return line + }; + + Context.prototype.baseToken = function (n) { + var this$1 = this; + + if (!this.baseTokens) { return null } + while (this.baseTokens[this.baseTokenPos] <= n) + { this$1.baseTokenPos += 2; } + var type = this.baseTokens[this.baseTokenPos + 1]; + return {type: type && type.replace(/( |^)overlay .*/, ""), + size: this.baseTokens[this.baseTokenPos] - n} + }; + + Context.prototype.nextLine = function () { + this.line++; + if (this.maxLookAhead > 0) { this.maxLookAhead--; } + }; + + Context.fromSaved = function (doc, saved, line) { + if (saved instanceof SavedContext) + { return new Context(doc, copyState(doc.mode, saved.state), line, saved.lookAhead) } + else + { return new Context(doc, copyState(doc.mode, saved), line) } + }; + + Context.prototype.save = function (copy) { + var state = copy !== false ? copyState(this.doc.mode, this.state) : this.state; + return this.maxLookAhead > 0 ? new SavedContext(state, this.maxLookAhead) : state + }; + + + // Compute a style array (an array starting with a mode generation + // -- for invalidation -- followed by pairs of end positions and + // style strings), which is used to highlight the tokens on the + // line. + function highlightLine(cm, line, context, forceToEnd) { + // A styles array always starts with a number identifying the + // mode/overlays that it is based on (for easy invalidation). + var st = [cm.state.modeGen], lineClasses = {}; + // Compute the base array of styles + runMode(cm, line.text, cm.doc.mode, context, function (end, style) { return st.push(end, style); }, + lineClasses, forceToEnd); + var state = context.state; + + // Run overlays, adjust style array. + var loop = function ( o ) { + context.baseTokens = st; + var overlay = cm.state.overlays[o], i = 1, at = 0; + context.state = true; + runMode(cm, line.text, overlay.mode, context, function (end, style) { + var start = i; + // Ensure there's a token end at the current position, and that i points at it + while (at < end) { + var i_end = st[i]; + if (i_end > end) + { st.splice(i, 1, end, st[i+1], i_end); } + i += 2; + at = Math.min(end, i_end); + } + if (!style) { return } + if (overlay.opaque) { + st.splice(start, i - start, end, "overlay " + style); + i = start + 2; + } else { + for (; start < i; start += 2) { + var cur = st[start+1]; + st[start+1] = (cur ? cur + " " : "") + "overlay " + style; + } + } + }, lineClasses); + context.state = state; + context.baseTokens = null; + context.baseTokenPos = 1; + }; + + for (var o = 0; o < cm.state.overlays.length; ++o) loop( o ); + + return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null} + } + + function getLineStyles(cm, line, updateFrontier) { + if (!line.styles || line.styles[0] != cm.state.modeGen) { + var context = getContextBefore(cm, lineNo(line)); + var resetState = line.text.length > cm.options.maxHighlightLength && copyState(cm.doc.mode, context.state); + var result = highlightLine(cm, line, context); + if (resetState) { context.state = resetState; } + line.stateAfter = context.save(!resetState); + line.styles = result.styles; + if (result.classes) { line.styleClasses = result.classes; } + else if (line.styleClasses) { line.styleClasses = null; } + if (updateFrontier === cm.doc.highlightFrontier) + { cm.doc.modeFrontier = Math.max(cm.doc.modeFrontier, ++cm.doc.highlightFrontier); } + } + return line.styles + } + + function getContextBefore(cm, n, precise) { + var doc = cm.doc, display = cm.display; + if (!doc.mode.startState) { return new Context(doc, true, n) } + var start = findStartLine(cm, n, precise); + var saved = start > doc.first && getLine(doc, start - 1).stateAfter; + var context = saved ? Context.fromSaved(doc, saved, start) : new Context(doc, startState(doc.mode), start); + + doc.iter(start, n, function (line) { + processLine(cm, line.text, context); + var pos = context.line; + line.stateAfter = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo ? context.save() : null; + context.nextLine(); + }); + if (precise) { doc.modeFrontier = context.line; } + return context + } + + // Lightweight form of highlight -- proceed over this line and + // update state, but don't save a style array. Used for lines that + // aren't currently visible. + function processLine(cm, text, context, startAt) { + var mode = cm.doc.mode; + var stream = new StringStream(text, cm.options.tabSize, context); + stream.start = stream.pos = startAt || 0; + if (text == "") { callBlankLine(mode, context.state); } + while (!stream.eol()) { + readToken(mode, stream, context.state); + stream.start = stream.pos; + } + } + + function callBlankLine(mode, state) { + if (mode.blankLine) { return mode.blankLine(state) } + if (!mode.innerMode) { return } + var inner = innerMode(mode, state); + if (inner.mode.blankLine) { return inner.mode.blankLine(inner.state) } + } + + function readToken(mode, stream, state, inner) { + for (var i = 0; i < 10; i++) { + if (inner) { inner[0] = innerMode(mode, state).mode; } + var style = mode.token(stream, state); + if (stream.pos > stream.start) { return style } + } + throw new Error("Mode " + mode.name + " failed to advance stream.") + } + + var Token = function(stream, type, state) { + this.start = stream.start; this.end = stream.pos; + this.string = stream.current(); + this.type = type || null; + this.state = state; + }; + + // Utility for getTokenAt and getLineTokens + function takeToken(cm, pos, precise, asArray) { + var doc = cm.doc, mode = doc.mode, style; + pos = clipPos(doc, pos); + var line = getLine(doc, pos.line), context = getContextBefore(cm, pos.line, precise); + var stream = new StringStream(line.text, cm.options.tabSize, context), tokens; + if (asArray) { tokens = []; } + while ((asArray || stream.pos < pos.ch) && !stream.eol()) { + stream.start = stream.pos; + style = readToken(mode, stream, context.state); + if (asArray) { tokens.push(new Token(stream, style, copyState(doc.mode, context.state))); } + } + return asArray ? tokens : new Token(stream, style, context.state) + } + + function extractLineClasses(type, output) { + if (type) { for (;;) { + var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/); + if (!lineClass) { break } + type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length); + var prop = lineClass[1] ? "bgClass" : "textClass"; + if (output[prop] == null) + { output[prop] = lineClass[2]; } + else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output[prop])) + { output[prop] += " " + lineClass[2]; } + } } + return type + } + + // Run the given mode's parser over a line, calling f for each token. + function runMode(cm, text, mode, context, f, lineClasses, forceToEnd) { + var flattenSpans = mode.flattenSpans; + if (flattenSpans == null) { flattenSpans = cm.options.flattenSpans; } + var curStart = 0, curStyle = null; + var stream = new StringStream(text, cm.options.tabSize, context), style; + var inner = cm.options.addModeClass && [null]; + if (text == "") { extractLineClasses(callBlankLine(mode, context.state), lineClasses); } + while (!stream.eol()) { + if (stream.pos > cm.options.maxHighlightLength) { + flattenSpans = false; + if (forceToEnd) { processLine(cm, text, context, stream.pos); } + stream.pos = text.length; + style = null; + } else { + style = extractLineClasses(readToken(mode, stream, context.state, inner), lineClasses); + } + if (inner) { + var mName = inner[0].name; + if (mName) { style = "m-" + (style ? mName + " " + style : mName); } + } + if (!flattenSpans || curStyle != style) { + while (curStart < stream.start) { + curStart = Math.min(stream.start, curStart + 5000); + f(curStart, curStyle); + } + curStyle = style; + } + stream.start = stream.pos; + } + while (curStart < stream.pos) { + // Webkit seems to refuse to render text nodes longer than 57444 + // characters, and returns inaccurate measurements in nodes + // starting around 5000 chars. + var pos = Math.min(stream.pos, curStart + 5000); + f(pos, curStyle); + curStart = pos; + } + } + + // Finds the line to start with when starting a parse. Tries to + // find a line with a stateAfter, so that it can start with a + // valid state. If that fails, it returns the line with the + // smallest indentation, which tends to need the least context to + // parse correctly. + function findStartLine(cm, n, precise) { + var minindent, minline, doc = cm.doc; + var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100); + for (var search = n; search > lim; --search) { + if (search <= doc.first) { return doc.first } + var line = getLine(doc, search - 1), after = line.stateAfter; + if (after && (!precise || search + (after instanceof SavedContext ? after.lookAhead : 0) <= doc.modeFrontier)) + { return search } + var indented = countColumn(line.text, null, cm.options.tabSize); + if (minline == null || minindent > indented) { + minline = search - 1; + minindent = indented; + } + } + return minline + } + + function retreatFrontier(doc, n) { + doc.modeFrontier = Math.min(doc.modeFrontier, n); + if (doc.highlightFrontier < n - 10) { return } + var start = doc.first; + for (var line = n - 1; line > start; line--) { + var saved = getLine(doc, line).stateAfter; + // change is on 3 + // state on line 1 looked ahead 2 -- so saw 3 + // test 1 + 2 < 3 should cover this + if (saved && (!(saved instanceof SavedContext) || line + saved.lookAhead < n)) { + start = line + 1; + break + } + } + doc.highlightFrontier = Math.min(doc.highlightFrontier, start); + } + + // LINE DATA STRUCTURE + + // Line objects. These hold state related to a line, including + // highlighting info (the styles array). + var Line = function(text, markedSpans, estimateHeight) { + this.text = text; + attachMarkedSpans(this, markedSpans); + this.height = estimateHeight ? estimateHeight(this) : 1; + }; + + Line.prototype.lineNo = function () { return lineNo(this) }; + eventMixin(Line); + + // Change the content (text, markers) of a line. Automatically + // invalidates cached information and tries to re-estimate the + // line's height. + function updateLine(line, text, markedSpans, estimateHeight) { + line.text = text; + if (line.stateAfter) { line.stateAfter = null; } + if (line.styles) { line.styles = null; } + if (line.order != null) { line.order = null; } + detachMarkedSpans(line); + attachMarkedSpans(line, markedSpans); + var estHeight = estimateHeight ? estimateHeight(line) : 1; + if (estHeight != line.height) { updateLineHeight(line, estHeight); } + } + + // Detach a line from the document tree and its markers. + function cleanUpLine(line) { + line.parent = null; + detachMarkedSpans(line); + } + + // Convert a style as returned by a mode (either null, or a string + // containing one or more styles) to a CSS style. This is cached, + // and also looks for line-wide styles. + var styleToClassCache = {}, styleToClassCacheWithMode = {}; + function interpretTokenStyle(style, options) { + if (!style || /^\s*$/.test(style)) { return null } + var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache; + return cache[style] || + (cache[style] = style.replace(/\S+/g, "cm-$&")) + } + + // Render the DOM representation of the text of a line. Also builds + // up a 'line map', which points at the DOM nodes that represent + // specific stretches of text, and is used by the measuring code. + // The returned object contains the DOM node, this map, and + // information about line-wide styles that were set by the mode. + function buildLineContent(cm, lineView) { + // The padding-right forces the element to have a 'border', which + // is needed on Webkit to be able to get line-level bounding + // rectangles for it (in measureChar). + var content = eltP("span", null, null, webkit ? "padding-right: .1px" : null); + var builder = {pre: eltP("pre", [content], "CodeMirror-line"), content: content, + col: 0, pos: 0, cm: cm, + trailingSpace: false, + splitSpaces: cm.getOption("lineWrapping")}; + lineView.measure = {}; + + // Iterate over the logical lines that make up this visual line. + for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) { + var line = i ? lineView.rest[i - 1] : lineView.line, order = (void 0); + builder.pos = 0; + builder.addToken = buildToken; + // Optionally wire in some hacks into the token-rendering + // algorithm, to deal with browser quirks. + if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line, cm.doc.direction))) + { builder.addToken = buildTokenBadBidi(builder.addToken, order); } + builder.map = []; + var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line); + insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate)); + if (line.styleClasses) { + if (line.styleClasses.bgClass) + { builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || ""); } + if (line.styleClasses.textClass) + { builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || ""); } + } + + // Ensure at least a single node is present, for measuring. + if (builder.map.length == 0) + { builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure))); } + + // Store the map and a cache object for the current logical line + if (i == 0) { + lineView.measure.map = builder.map; + lineView.measure.cache = {}; + } else { + (lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map) + ;(lineView.measure.caches || (lineView.measure.caches = [])).push({}); + } + } + + // See issue #2901 + if (webkit) { + var last = builder.content.lastChild; + if (/\bcm-tab\b/.test(last.className) || (last.querySelector && last.querySelector(".cm-tab"))) + { builder.content.className = "cm-tab-wrap-hack"; } + } + + signal(cm, "renderLine", cm, lineView.line, builder.pre); + if (builder.pre.className) + { builder.textClass = joinClasses(builder.pre.className, builder.textClass || ""); } + + return builder + } + + function defaultSpecialCharPlaceholder(ch) { + var token = elt("span", "\u2022", "cm-invalidchar"); + token.title = "\\u" + ch.charCodeAt(0).toString(16); + token.setAttribute("aria-label", token.title); + return token + } + + // Build up the DOM representation for a single token, and add it to + // the line map. Takes care to render special characters separately. + function buildToken(builder, text, style, startStyle, endStyle, css, attributes) { + if (!text) { return } + var displayText = builder.splitSpaces ? splitSpaces(text, builder.trailingSpace) : text; + var special = builder.cm.state.specialChars, mustWrap = false; + var content; + if (!special.test(text)) { + builder.col += text.length; + content = document.createTextNode(displayText); + builder.map.push(builder.pos, builder.pos + text.length, content); + if (ie && ie_version < 9) { mustWrap = true; } + builder.pos += text.length; + } else { + content = document.createDocumentFragment(); + var pos = 0; + while (true) { + special.lastIndex = pos; + var m = special.exec(text); + var skipped = m ? m.index - pos : text.length - pos; + if (skipped) { + var txt = document.createTextNode(displayText.slice(pos, pos + skipped)); + if (ie && ie_version < 9) { content.appendChild(elt("span", [txt])); } + else { content.appendChild(txt); } + builder.map.push(builder.pos, builder.pos + skipped, txt); + builder.col += skipped; + builder.pos += skipped; + } + if (!m) { break } + pos += skipped + 1; + var txt$1 = (void 0); + if (m[0] == "\t") { + var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize; + txt$1 = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); + txt$1.setAttribute("role", "presentation"); + txt$1.setAttribute("cm-text", "\t"); + builder.col += tabWidth; + } else if (m[0] == "\r" || m[0] == "\n") { + txt$1 = content.appendChild(elt("span", m[0] == "\r" ? "\u240d" : "\u2424", "cm-invalidchar")); + txt$1.setAttribute("cm-text", m[0]); + builder.col += 1; + } else { + txt$1 = builder.cm.options.specialCharPlaceholder(m[0]); + txt$1.setAttribute("cm-text", m[0]); + if (ie && ie_version < 9) { content.appendChild(elt("span", [txt$1])); } + else { content.appendChild(txt$1); } + builder.col += 1; + } + builder.map.push(builder.pos, builder.pos + 1, txt$1); + builder.pos++; + } + } + builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32; + if (style || startStyle || endStyle || mustWrap || css) { + var fullStyle = style || ""; + if (startStyle) { fullStyle += startStyle; } + if (endStyle) { fullStyle += endStyle; } + var token = elt("span", [content], fullStyle, css); + if (attributes) { + for (var attr in attributes) { if (attributes.hasOwnProperty(attr) && attr != "style" && attr != "class") + { token.setAttribute(attr, attributes[attr]); } } + } + return builder.content.appendChild(token) + } + builder.content.appendChild(content); + } + + // Change some spaces to NBSP to prevent the browser from collapsing + // trailing spaces at the end of a line when rendering text (issue #1362). + function splitSpaces(text, trailingBefore) { + if (text.length > 1 && !/ /.test(text)) { return text } + var spaceBefore = trailingBefore, result = ""; + for (var i = 0; i < text.length; i++) { + var ch = text.charAt(i); + if (ch == " " && spaceBefore && (i == text.length - 1 || text.charCodeAt(i + 1) == 32)) + { ch = "\u00a0"; } + result += ch; + spaceBefore = ch == " "; + } + return result + } + + // Work around nonsense dimensions being reported for stretches of + // right-to-left text. + function buildTokenBadBidi(inner, order) { + return function (builder, text, style, startStyle, endStyle, css, attributes) { + style = style ? style + " cm-force-border" : "cm-force-border"; + var start = builder.pos, end = start + text.length; + for (;;) { + // Find the part that overlaps with the start of this text + var part = (void 0); + for (var i = 0; i < order.length; i++) { + part = order[i]; + if (part.to > start && part.from <= start) { break } + } + if (part.to >= end) { return inner(builder, text, style, startStyle, endStyle, css, attributes) } + inner(builder, text.slice(0, part.to - start), style, startStyle, null, css, attributes); + startStyle = null; + text = text.slice(part.to - start); + start = part.to; + } + } + } + + function buildCollapsedSpan(builder, size, marker, ignoreWidget) { + var widget = !ignoreWidget && marker.widgetNode; + if (widget) { builder.map.push(builder.pos, builder.pos + size, widget); } + if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) { + if (!widget) + { widget = builder.content.appendChild(document.createElement("span")); } + widget.setAttribute("cm-marker", marker.id); + } + if (widget) { + builder.cm.display.input.setUneditable(widget); + builder.content.appendChild(widget); + } + builder.pos += size; + builder.trailingSpace = false; + } + + // Outputs a number of spans to make up a line, taking highlighting + // and marked text into account. + function insertLineContent(line, builder, styles) { + var spans = line.markedSpans, allText = line.text, at = 0; + if (!spans) { + for (var i$1 = 1; i$1 < styles.length; i$1+=2) + { builder.addToken(builder, allText.slice(at, at = styles[i$1]), interpretTokenStyle(styles[i$1+1], builder.cm.options)); } + return + } + + var len = allText.length, pos = 0, i = 1, text = "", style, css; + var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, collapsed, attributes; + for (;;) { + if (nextChange == pos) { // Update current marker set + spanStyle = spanEndStyle = spanStartStyle = css = ""; + attributes = null; + collapsed = null; nextChange = Infinity; + var foundBookmarks = [], endStyles = (void 0); + for (var j = 0; j < spans.length; ++j) { + var sp = spans[j], m = sp.marker; + if (m.type == "bookmark" && sp.from == pos && m.widgetNode) { + foundBookmarks.push(m); + } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapsed && sp.to == pos && sp.from == pos)) { + if (sp.to != null && sp.to != pos && nextChange > sp.to) { + nextChange = sp.to; + spanEndStyle = ""; + } + if (m.className) { spanStyle += " " + m.className; } + if (m.css) { css = (css ? css + ";" : "") + m.css; } + if (m.startStyle && sp.from == pos) { spanStartStyle += " " + m.startStyle; } + if (m.endStyle && sp.to == nextChange) { (endStyles || (endStyles = [])).push(m.endStyle, sp.to); } + // support for the old title property + // https://github.com/codemirror/CodeMirror/pull/5673 + if (m.title) { (attributes || (attributes = {})).title = m.title; } + if (m.attributes) { + for (var attr in m.attributes) + { (attributes || (attributes = {}))[attr] = m.attributes[attr]; } + } + if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0)) + { collapsed = sp; } + } else if (sp.from > pos && nextChange > sp.from) { + nextChange = sp.from; + } + } + if (endStyles) { for (var j$1 = 0; j$1 < endStyles.length; j$1 += 2) + { if (endStyles[j$1 + 1] == nextChange) { spanEndStyle += " " + endStyles[j$1]; } } } + + if (!collapsed || collapsed.from == pos) { for (var j$2 = 0; j$2 < foundBookmarks.length; ++j$2) + { buildCollapsedSpan(builder, 0, foundBookmarks[j$2]); } } + if (collapsed && (collapsed.from || 0) == pos) { + buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos, + collapsed.marker, collapsed.from == null); + if (collapsed.to == null) { return } + if (collapsed.to == pos) { collapsed = false; } + } + } + if (pos >= len) { break } + + var upto = Math.min(len, nextChange); + while (true) { + if (text) { + var end = pos + text.length; + if (!collapsed) { + var tokenText = end > upto ? text.slice(0, upto - pos) : text; + builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, + spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", css, attributes); + } + if (end >= upto) {text = text.slice(upto - pos); pos = upto; break} + pos = end; + spanStartStyle = ""; + } + text = allText.slice(at, at = styles[i++]); + style = interpretTokenStyle(styles[i++], builder.cm.options); + } + } + } + + + // These objects are used to represent the visible (currently drawn) + // part of the document. A LineView may correspond to multiple + // logical lines, if those are connected by collapsed ranges. + function LineView(doc, line, lineN) { + // The starting line + this.line = line; + // Continuing lines, if any + this.rest = visualLineContinued(line); + // Number of logical lines in this visual line + this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1; + this.node = this.text = null; + this.hidden = lineIsHidden(doc, line); + } + + // Create a range of LineView objects for the given lines. + function buildViewArray(cm, from, to) { + var array = [], nextPos; + for (var pos = from; pos < to; pos = nextPos) { + var view = new LineView(cm.doc, getLine(cm.doc, pos), pos); + nextPos = pos + view.size; + array.push(view); + } + return array + } + + var operationGroup = null; + + function pushOperation(op) { + if (operationGroup) { + operationGroup.ops.push(op); + } else { + op.ownsGroup = operationGroup = { + ops: [op], + delayedCallbacks: [] + }; + } + } + + function fireCallbacksForOps(group) { + // Calls delayed callbacks and cursorActivity handlers until no + // new ones appear + var callbacks = group.delayedCallbacks, i = 0; + do { + for (; i < callbacks.length; i++) + { callbacks[i].call(null); } + for (var j = 0; j < group.ops.length; j++) { + var op = group.ops[j]; + if (op.cursorActivityHandlers) + { while (op.cursorActivityCalled < op.cursorActivityHandlers.length) + { op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.cm); } } + } + } while (i < callbacks.length) + } + + function finishOperation(op, endCb) { + var group = op.ownsGroup; + if (!group) { return } + + try { fireCallbacksForOps(group); } + finally { + operationGroup = null; + endCb(group); + } + } + + var orphanDelayedCallbacks = null; + + // Often, we want to signal events at a point where we are in the + // middle of some work, but don't want the handler to start calling + // other methods on the editor, which might be in an inconsistent + // state or simply not expect any other events to happen. + // signalLater looks whether there are any handlers, and schedules + // them to be executed when the last operation ends, or, if no + // operation is active, when a timeout fires. + function signalLater(emitter, type /*, values...*/) { + var arr = getHandlers(emitter, type); + if (!arr.length) { return } + var args = Array.prototype.slice.call(arguments, 2), list; + if (operationGroup) { + list = operationGroup.delayedCallbacks; + } else if (orphanDelayedCallbacks) { + list = orphanDelayedCallbacks; + } else { + list = orphanDelayedCallbacks = []; + setTimeout(fireOrphanDelayed, 0); + } + var loop = function ( i ) { + list.push(function () { return arr[i].apply(null, args); }); + }; + + for (var i = 0; i < arr.length; ++i) + loop( i ); + } + + function fireOrphanDelayed() { + var delayed = orphanDelayedCallbacks; + orphanDelayedCallbacks = null; + for (var i = 0; i < delayed.length; ++i) { delayed[i](); } + } + + // When an aspect of a line changes, a string is added to + // lineView.changes. This updates the relevant part of the line's + // DOM structure. + function updateLineForChanges(cm, lineView, lineN, dims) { + for (var j = 0; j < lineView.changes.length; j++) { + var type = lineView.changes[j]; + if (type == "text") { updateLineText(cm, lineView); } + else if (type == "gutter") { updateLineGutter(cm, lineView, lineN, dims); } + else if (type == "class") { updateLineClasses(cm, lineView); } + else if (type == "widget") { updateLineWidgets(cm, lineView, dims); } + } + lineView.changes = null; + } + + // Lines with gutter elements, widgets or a background class need to + // be wrapped, and have the extra elements added to the wrapper div + function ensureLineWrapped(lineView) { + if (lineView.node == lineView.text) { + lineView.node = elt("div", null, null, "position: relative"); + if (lineView.text.parentNode) + { lineView.text.parentNode.replaceChild(lineView.node, lineView.text); } + lineView.node.appendChild(lineView.text); + if (ie && ie_version < 8) { lineView.node.style.zIndex = 2; } + } + return lineView.node + } + + function updateLineBackground(cm, lineView) { + var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass; + if (cls) { cls += " CodeMirror-linebackground"; } + if (lineView.background) { + if (cls) { lineView.background.className = cls; } + else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null; } + } else if (cls) { + var wrap = ensureLineWrapped(lineView); + lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild); + cm.display.input.setUneditable(lineView.background); + } + } + + // Wrapper around buildLineContent which will reuse the structure + // in display.externalMeasured when possible. + function getLineContent(cm, lineView) { + var ext = cm.display.externalMeasured; + if (ext && ext.line == lineView.line) { + cm.display.externalMeasured = null; + lineView.measure = ext.measure; + return ext.built + } + return buildLineContent(cm, lineView) + } + + // Redraw the line's text. Interacts with the background and text + // classes because the mode may output tokens that influence these + // classes. + function updateLineText(cm, lineView) { + var cls = lineView.text.className; + var built = getLineContent(cm, lineView); + if (lineView.text == lineView.node) { lineView.node = built.pre; } + lineView.text.parentNode.replaceChild(built.pre, lineView.text); + lineView.text = built.pre; + if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) { + lineView.bgClass = built.bgClass; + lineView.textClass = built.textClass; + updateLineClasses(cm, lineView); + } else if (cls) { + lineView.text.className = cls; + } + } + + function updateLineClasses(cm, lineView) { + updateLineBackground(cm, lineView); + if (lineView.line.wrapClass) + { ensureLineWrapped(lineView).className = lineView.line.wrapClass; } + else if (lineView.node != lineView.text) + { lineView.node.className = ""; } + var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass; + lineView.text.className = textClass || ""; + } + + function updateLineGutter(cm, lineView, lineN, dims) { + if (lineView.gutter) { + lineView.node.removeChild(lineView.gutter); + lineView.gutter = null; + } + if (lineView.gutterBackground) { + lineView.node.removeChild(lineView.gutterBackground); + lineView.gutterBackground = null; + } + if (lineView.line.gutterClass) { + var wrap = ensureLineWrapped(lineView); + lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background " + lineView.line.gutterClass, + ("left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px; width: " + (dims.gutterTotalWidth) + "px")); + cm.display.input.setUneditable(lineView.gutterBackground); + wrap.insertBefore(lineView.gutterBackground, lineView.text); + } + var markers = lineView.line.gutterMarkers; + if (cm.options.lineNumbers || markers) { + var wrap$1 = ensureLineWrapped(lineView); + var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", ("left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px")); + cm.display.input.setUneditable(gutterWrap); + wrap$1.insertBefore(gutterWrap, lineView.text); + if (lineView.line.gutterClass) + { gutterWrap.className += " " + lineView.line.gutterClass; } + if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) + { lineView.lineNumber = gutterWrap.appendChild( + elt("div", lineNumberFor(cm.options, lineN), + "CodeMirror-linenumber CodeMirror-gutter-elt", + ("left: " + (dims.gutterLeft["CodeMirror-linenumbers"]) + "px; width: " + (cm.display.lineNumInnerWidth) + "px"))); } + if (markers) { for (var k = 0; k < cm.options.gutters.length; ++k) { + var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id]; + if (found) + { gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", + ("left: " + (dims.gutterLeft[id]) + "px; width: " + (dims.gutterWidth[id]) + "px"))); } + } } + } + } + + function updateLineWidgets(cm, lineView, dims) { + if (lineView.alignable) { lineView.alignable = null; } + for (var node = lineView.node.firstChild, next = (void 0); node; node = next) { + next = node.nextSibling; + if (node.className == "CodeMirror-linewidget") + { lineView.node.removeChild(node); } + } + insertLineWidgets(cm, lineView, dims); + } + + // Build a line's DOM representation from scratch + function buildLineElement(cm, lineView, lineN, dims) { + var built = getLineContent(cm, lineView); + lineView.text = lineView.node = built.pre; + if (built.bgClass) { lineView.bgClass = built.bgClass; } + if (built.textClass) { lineView.textClass = built.textClass; } + + updateLineClasses(cm, lineView); + updateLineGutter(cm, lineView, lineN, dims); + insertLineWidgets(cm, lineView, dims); + return lineView.node + } + + // A lineView may contain multiple logical lines (when merged by + // collapsed spans). The widgets for all of them need to be drawn. + function insertLineWidgets(cm, lineView, dims) { + insertLineWidgetsFor(cm, lineView.line, lineView, dims, true); + if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++) + { insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false); } } + } + + function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) { + if (!line.widgets) { return } + var wrap = ensureLineWrapped(lineView); + for (var i = 0, ws = line.widgets; i < ws.length; ++i) { + var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget"); + if (!widget.handleMouseEvents) { node.setAttribute("cm-ignore-events", "true"); } + positionLineWidget(widget, node, lineView, dims); + cm.display.input.setUneditable(node); + if (allowAbove && widget.above) + { wrap.insertBefore(node, lineView.gutter || lineView.text); } + else + { wrap.appendChild(node); } + signalLater(widget, "redraw"); + } + } + + function positionLineWidget(widget, node, lineView, dims) { + if (widget.noHScroll) { + (lineView.alignable || (lineView.alignable = [])).push(node); + var width = dims.wrapperWidth; + node.style.left = dims.fixedPos + "px"; + if (!widget.coverGutter) { + width -= dims.gutterTotalWidth; + node.style.paddingLeft = dims.gutterTotalWidth + "px"; + } + node.style.width = width + "px"; + } + if (widget.coverGutter) { + node.style.zIndex = 5; + node.style.position = "relative"; + if (!widget.noHScroll) { node.style.marginLeft = -dims.gutterTotalWidth + "px"; } + } + } + + function widgetHeight(widget) { + if (widget.height != null) { return widget.height } + var cm = widget.doc.cm; + if (!cm) { return 0 } + if (!contains(document.body, widget.node)) { + var parentStyle = "position: relative;"; + if (widget.coverGutter) + { parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;"; } + if (widget.noHScroll) + { parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;"; } + removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle)); + } + return widget.height = widget.node.parentNode.offsetHeight + } + + // Return true when the given mouse event happened in a widget + function eventInWidget(display, e) { + for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { + if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true") || + (n.parentNode == display.sizer && n != display.mover)) + { return true } + } + } + + // POSITION MEASUREMENT + + function paddingTop(display) {return display.lineSpace.offsetTop} + function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight} + function paddingH(display) { + if (display.cachedPaddingH) { return display.cachedPaddingH } + var e = removeChildrenAndAdd(display.measure, elt("pre", "x")); + var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle; + var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)}; + if (!isNaN(data.left) && !isNaN(data.right)) { display.cachedPaddingH = data; } + return data + } + + function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth } + function displayWidth(cm) { + return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth + } + function displayHeight(cm) { + return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight + } + + // Ensure the lineView.wrapping.heights array is populated. This is + // an array of bottom offsets for the lines that make up a drawn + // line. When lineWrapping is on, there might be more than one + // height. + function ensureLineHeights(cm, lineView, rect) { + var wrapping = cm.options.lineWrapping; + var curWidth = wrapping && displayWidth(cm); + if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) { + var heights = lineView.measure.heights = []; + if (wrapping) { + lineView.measure.width = curWidth; + var rects = lineView.text.firstChild.getClientRects(); + for (var i = 0; i < rects.length - 1; i++) { + var cur = rects[i], next = rects[i + 1]; + if (Math.abs(cur.bottom - next.bottom) > 2) + { heights.push((cur.bottom + next.top) / 2 - rect.top); } + } + } + heights.push(rect.bottom - rect.top); + } + } + + // Find a line map (mapping character offsets to text nodes) and a + // measurement cache for the given line number. (A line view might + // contain multiple lines when collapsed ranges are present.) + function mapFromLineView(lineView, line, lineN) { + if (lineView.line == line) + { return {map: lineView.measure.map, cache: lineView.measure.cache} } + for (var i = 0; i < lineView.rest.length; i++) + { if (lineView.rest[i] == line) + { return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]} } } + for (var i$1 = 0; i$1 < lineView.rest.length; i$1++) + { if (lineNo(lineView.rest[i$1]) > lineN) + { return {map: lineView.measure.maps[i$1], cache: lineView.measure.caches[i$1], before: true} } } + } + + // Render a line into the hidden node display.externalMeasured. Used + // when measurement is needed for a line that's not in the viewport. + function updateExternalMeasurement(cm, line) { + line = visualLine(line); + var lineN = lineNo(line); + var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN); + view.lineN = lineN; + var built = view.built = buildLineContent(cm, view); + view.text = built.pre; + removeChildrenAndAdd(cm.display.lineMeasure, built.pre); + return view + } + + // Get a {top, bottom, left, right} box (in line-local coordinates) + // for a given character. + function measureChar(cm, line, ch, bias) { + return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias) + } + + // Find a line view that corresponds to the given line number. + function findViewForLine(cm, lineN) { + if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo) + { return cm.display.view[findViewIndex(cm, lineN)] } + var ext = cm.display.externalMeasured; + if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size) + { return ext } + } + + // Measurement can be split in two steps, the set-up work that + // applies to the whole line, and the measurement of the actual + // character. Functions like coordsChar, that need to do a lot of + // measurements in a row, can thus ensure that the set-up work is + // only done once. + function prepareMeasureForLine(cm, line) { + var lineN = lineNo(line); + var view = findViewForLine(cm, lineN); + if (view && !view.text) { + view = null; + } else if (view && view.changes) { + updateLineForChanges(cm, view, lineN, getDimensions(cm)); + cm.curOp.forceUpdate = true; + } + if (!view) + { view = updateExternalMeasurement(cm, line); } + + var info = mapFromLineView(view, line, lineN); + return { + line: line, view: view, rect: null, + map: info.map, cache: info.cache, before: info.before, + hasHeights: false + } + } + + // Given a prepared measurement object, measures the position of an + // actual character (or fetches it from the cache). + function measureCharPrepared(cm, prepared, ch, bias, varHeight) { + if (prepared.before) { ch = -1; } + var key = ch + (bias || ""), found; + if (prepared.cache.hasOwnProperty(key)) { + found = prepared.cache[key]; + } else { + if (!prepared.rect) + { prepared.rect = prepared.view.text.getBoundingClientRect(); } + if (!prepared.hasHeights) { + ensureLineHeights(cm, prepared.view, prepared.rect); + prepared.hasHeights = true; + } + found = measureCharInner(cm, prepared, ch, bias); + if (!found.bogus) { prepared.cache[key] = found; } + } + return {left: found.left, right: found.right, + top: varHeight ? found.rtop : found.top, + bottom: varHeight ? found.rbottom : found.bottom} + } + + var nullRect = {left: 0, right: 0, top: 0, bottom: 0}; + + function nodeAndOffsetInLineMap(map$$1, ch, bias) { + var node, start, end, collapse, mStart, mEnd; + // First, search the line map for the text node corresponding to, + // or closest to, the target character. + for (var i = 0; i < map$$1.length; i += 3) { + mStart = map$$1[i]; + mEnd = map$$1[i + 1]; + if (ch < mStart) { + start = 0; end = 1; + collapse = "left"; + } else if (ch < mEnd) { + start = ch - mStart; + end = start + 1; + } else if (i == map$$1.length - 3 || ch == mEnd && map$$1[i + 3] > ch) { + end = mEnd - mStart; + start = end - 1; + if (ch >= mEnd) { collapse = "right"; } + } + if (start != null) { + node = map$$1[i + 2]; + if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right")) + { collapse = bias; } + if (bias == "left" && start == 0) + { while (i && map$$1[i - 2] == map$$1[i - 3] && map$$1[i - 1].insertLeft) { + node = map$$1[(i -= 3) + 2]; + collapse = "left"; + } } + if (bias == "right" && start == mEnd - mStart) + { while (i < map$$1.length - 3 && map$$1[i + 3] == map$$1[i + 4] && !map$$1[i + 5].insertLeft) { + node = map$$1[(i += 3) + 2]; + collapse = "right"; + } } + break + } + } + return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd} + } + + function getUsefulRect(rects, bias) { + var rect = nullRect; + if (bias == "left") { for (var i = 0; i < rects.length; i++) { + if ((rect = rects[i]).left != rect.right) { break } + } } else { for (var i$1 = rects.length - 1; i$1 >= 0; i$1--) { + if ((rect = rects[i$1]).left != rect.right) { break } + } } + return rect + } + + function measureCharInner(cm, prepared, ch, bias) { + var place = nodeAndOffsetInLineMap(prepared.map, ch, bias); + var node = place.node, start = place.start, end = place.end, collapse = place.collapse; + + var rect; + if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates. + for (var i$1 = 0; i$1 < 4; i$1++) { // Retry a maximum of 4 times when nonsense rectangles are returned + while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) { --start; } + while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) { ++end; } + if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart) + { rect = node.parentNode.getBoundingClientRect(); } + else + { rect = getUsefulRect(range(node, start, end).getClientRects(), bias); } + if (rect.left || rect.right || start == 0) { break } + end = start; + start = start - 1; + collapse = "right"; + } + if (ie && ie_version < 11) { rect = maybeUpdateRectForZooming(cm.display.measure, rect); } + } else { // If it is a widget, simply get the box for the whole widget. + if (start > 0) { collapse = bias = "right"; } + var rects; + if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1) + { rect = rects[bias == "right" ? rects.length - 1 : 0]; } + else + { rect = node.getBoundingClientRect(); } + } + if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) { + var rSpan = node.parentNode.getClientRects()[0]; + if (rSpan) + { rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom}; } + else + { rect = nullRect; } + } + + var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top; + var mid = (rtop + rbot) / 2; + var heights = prepared.view.measure.heights; + var i = 0; + for (; i < heights.length - 1; i++) + { if (mid < heights[i]) { break } } + var top = i ? heights[i - 1] : 0, bot = heights[i]; + var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left, + right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left, + top: top, bottom: bot}; + if (!rect.left && !rect.right) { result.bogus = true; } + if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot; } + + return result + } + + // Work around problem with bounding client rects on ranges being + // returned incorrectly when zoomed on IE10 and below. + function maybeUpdateRectForZooming(measure, rect) { + if (!window.screen || screen.logicalXDPI == null || + screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure)) + { return rect } + var scaleX = screen.logicalXDPI / screen.deviceXDPI; + var scaleY = screen.logicalYDPI / screen.deviceYDPI; + return {left: rect.left * scaleX, right: rect.right * scaleX, + top: rect.top * scaleY, bottom: rect.bottom * scaleY} + } + + function clearLineMeasurementCacheFor(lineView) { + if (lineView.measure) { + lineView.measure.cache = {}; + lineView.measure.heights = null; + if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++) + { lineView.measure.caches[i] = {}; } } + } + } + + function clearLineMeasurementCache(cm) { + cm.display.externalMeasure = null; + removeChildren(cm.display.lineMeasure); + for (var i = 0; i < cm.display.view.length; i++) + { clearLineMeasurementCacheFor(cm.display.view[i]); } + } + + function clearCaches(cm) { + clearLineMeasurementCache(cm); + cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null; + if (!cm.options.lineWrapping) { cm.display.maxLineChanged = true; } + cm.display.lineNumChars = null; + } + + function pageScrollX() { + // Work around https://bugs.chromium.org/p/chromium/issues/detail?id=489206 + // which causes page_Offset and bounding client rects to use + // different reference viewports and invalidate our calculations. + if (chrome && android) { return -(document.body.getBoundingClientRect().left - parseInt(getComputedStyle(document.body).marginLeft)) } + return window.pageXOffset || (document.documentElement || document.body).scrollLeft + } + function pageScrollY() { + if (chrome && android) { return -(document.body.getBoundingClientRect().top - parseInt(getComputedStyle(document.body).marginTop)) } + return window.pageYOffset || (document.documentElement || document.body).scrollTop + } + + function widgetTopHeight(lineObj) { + var height = 0; + if (lineObj.widgets) { for (var i = 0; i < lineObj.widgets.length; ++i) { if (lineObj.widgets[i].above) + { height += widgetHeight(lineObj.widgets[i]); } } } + return height + } + + // Converts a {top, bottom, left, right} box from line-local + // coordinates into another coordinate system. Context may be one of + // "line", "div" (display.lineDiv), "local"./null (editor), "window", + // or "page". + function intoCoordSystem(cm, lineObj, rect, context, includeWidgets) { + if (!includeWidgets) { + var height = widgetTopHeight(lineObj); + rect.top += height; rect.bottom += height; + } + if (context == "line") { return rect } + if (!context) { context = "local"; } + var yOff = heightAtLine(lineObj); + if (context == "local") { yOff += paddingTop(cm.display); } + else { yOff -= cm.display.viewOffset; } + if (context == "page" || context == "window") { + var lOff = cm.display.lineSpace.getBoundingClientRect(); + yOff += lOff.top + (context == "window" ? 0 : pageScrollY()); + var xOff = lOff.left + (context == "window" ? 0 : pageScrollX()); + rect.left += xOff; rect.right += xOff; + } + rect.top += yOff; rect.bottom += yOff; + return rect + } + + // Coverts a box from "div" coords to another coordinate system. + // Context may be "window", "page", "div", or "local"./null. + function fromCoordSystem(cm, coords, context) { + if (context == "div") { return coords } + var left = coords.left, top = coords.top; + // First move into "page" coordinate system + if (context == "page") { + left -= pageScrollX(); + top -= pageScrollY(); + } else if (context == "local" || !context) { + var localBox = cm.display.sizer.getBoundingClientRect(); + left += localBox.left; + top += localBox.top; + } + + var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect(); + return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top} + } + + function charCoords(cm, pos, context, lineObj, bias) { + if (!lineObj) { lineObj = getLine(cm.doc, pos.line); } + return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context) + } + + // Returns a box for a given cursor position, which may have an + // 'other' property containing the position of the secondary cursor + // on a bidi boundary. + // A cursor Pos(line, char, "before") is on the same visual line as `char - 1` + // and after `char - 1` in writing order of `char - 1` + // A cursor Pos(line, char, "after") is on the same visual line as `char` + // and before `char` in writing order of `char` + // Examples (upper-case letters are RTL, lower-case are LTR): + // Pos(0, 1, ...) + // before after + // ab a|b a|b + // aB a|B aB| + // Ab |Ab A|b + // AB B|A B|A + // Every position after the last character on a line is considered to stick + // to the last character on the line. + function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) { + lineObj = lineObj || getLine(cm.doc, pos.line); + if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj); } + function get(ch, right) { + var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight); + if (right) { m.left = m.right; } else { m.right = m.left; } + return intoCoordSystem(cm, lineObj, m, context) + } + var order = getOrder(lineObj, cm.doc.direction), ch = pos.ch, sticky = pos.sticky; + if (ch >= lineObj.text.length) { + ch = lineObj.text.length; + sticky = "before"; + } else if (ch <= 0) { + ch = 0; + sticky = "after"; + } + if (!order) { return get(sticky == "before" ? ch - 1 : ch, sticky == "before") } + + function getBidi(ch, partPos, invert) { + var part = order[partPos], right = part.level == 1; + return get(invert ? ch - 1 : ch, right != invert) + } + var partPos = getBidiPartAt(order, ch, sticky); + var other = bidiOther; + var val = getBidi(ch, partPos, sticky == "before"); + if (other != null) { val.other = getBidi(ch, other, sticky != "before"); } + return val + } + + // Used to cheaply estimate the coordinates for a position. Used for + // intermediate scroll updates. + function estimateCoords(cm, pos) { + var left = 0; + pos = clipPos(cm.doc, pos); + if (!cm.options.lineWrapping) { left = charWidth(cm.display) * pos.ch; } + var lineObj = getLine(cm.doc, pos.line); + var top = heightAtLine(lineObj) + paddingTop(cm.display); + return {left: left, right: left, top: top, bottom: top + lineObj.height} + } + + // Positions returned by coordsChar contain some extra information. + // xRel is the relative x position of the input coordinates compared + // to the found position (so xRel > 0 means the coordinates are to + // the right of the character position, for example). When outside + // is true, that means the coordinates lie outside the line's + // vertical range. + function PosWithInfo(line, ch, sticky, outside, xRel) { + var pos = Pos(line, ch, sticky); + pos.xRel = xRel; + if (outside) { pos.outside = true; } + return pos + } + + // Compute the character position closest to the given coordinates. + // Input must be lineSpace-local ("div" coordinate system). + function coordsChar(cm, x, y) { + var doc = cm.doc; + y += cm.display.viewOffset; + if (y < 0) { return PosWithInfo(doc.first, 0, null, true, -1) } + var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1; + if (lineN > last) + { return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, null, true, 1) } + if (x < 0) { x = 0; } + + var lineObj = getLine(doc, lineN); + for (;;) { + var found = coordsCharInner(cm, lineObj, lineN, x, y); + var collapsed = collapsedSpanAround(lineObj, found.ch + (found.xRel > 0 ? 1 : 0)); + if (!collapsed) { return found } + var rangeEnd = collapsed.find(1); + if (rangeEnd.line == lineN) { return rangeEnd } + lineObj = getLine(doc, lineN = rangeEnd.line); + } + } + + function wrappedLineExtent(cm, lineObj, preparedMeasure, y) { + y -= widgetTopHeight(lineObj); + var end = lineObj.text.length; + var begin = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch - 1).bottom <= y; }, end, 0); + end = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch).top > y; }, begin, end); + return {begin: begin, end: end} + } + + function wrappedLineExtentChar(cm, lineObj, preparedMeasure, target) { + if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj); } + var targetTop = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, target), "line").top; + return wrappedLineExtent(cm, lineObj, preparedMeasure, targetTop) + } + + // Returns true if the given side of a box is after the given + // coordinates, in top-to-bottom, left-to-right order. + function boxIsAfter(box, x, y, left) { + return box.bottom <= y ? false : box.top > y ? true : (left ? box.left : box.right) > x + } + + function coordsCharInner(cm, lineObj, lineNo$$1, x, y) { + // Move y into line-local coordinate space + y -= heightAtLine(lineObj); + var preparedMeasure = prepareMeasureForLine(cm, lineObj); + // When directly calling `measureCharPrepared`, we have to adjust + // for the widgets at this line. + var widgetHeight$$1 = widgetTopHeight(lineObj); + var begin = 0, end = lineObj.text.length, ltr = true; + + var order = getOrder(lineObj, cm.doc.direction); + // If the line isn't plain left-to-right text, first figure out + // which bidi section the coordinates fall into. + if (order) { + var part = (cm.options.lineWrapping ? coordsBidiPartWrapped : coordsBidiPart) + (cm, lineObj, lineNo$$1, preparedMeasure, order, x, y); + ltr = part.level != 1; + // The awkward -1 offsets are needed because findFirst (called + // on these below) will treat its first bound as inclusive, + // second as exclusive, but we want to actually address the + // characters in the part's range + begin = ltr ? part.from : part.to - 1; + end = ltr ? part.to : part.from - 1; + } + + // A binary search to find the first character whose bounding box + // starts after the coordinates. If we run across any whose box wrap + // the coordinates, store that. + var chAround = null, boxAround = null; + var ch = findFirst(function (ch) { + var box = measureCharPrepared(cm, preparedMeasure, ch); + box.top += widgetHeight$$1; box.bottom += widgetHeight$$1; + if (!boxIsAfter(box, x, y, false)) { return false } + if (box.top <= y && box.left <= x) { + chAround = ch; + boxAround = box; + } + return true + }, begin, end); + + var baseX, sticky, outside = false; + // If a box around the coordinates was found, use that + if (boxAround) { + // Distinguish coordinates nearer to the left or right side of the box + var atLeft = x - boxAround.left < boxAround.right - x, atStart = atLeft == ltr; + ch = chAround + (atStart ? 0 : 1); + sticky = atStart ? "after" : "before"; + baseX = atLeft ? boxAround.left : boxAround.right; + } else { + // (Adjust for extended bound, if necessary.) + if (!ltr && (ch == end || ch == begin)) { ch++; } + // To determine which side to associate with, get the box to the + // left of the character and compare it's vertical position to the + // coordinates + sticky = ch == 0 ? "after" : ch == lineObj.text.length ? "before" : + (measureCharPrepared(cm, preparedMeasure, ch - (ltr ? 1 : 0)).bottom + widgetHeight$$1 <= y) == ltr ? + "after" : "before"; + // Now get accurate coordinates for this place, in order to get a + // base X position + var coords = cursorCoords(cm, Pos(lineNo$$1, ch, sticky), "line", lineObj, preparedMeasure); + baseX = coords.left; + outside = y < coords.top || y >= coords.bottom; + } + + ch = skipExtendingChars(lineObj.text, ch, 1); + return PosWithInfo(lineNo$$1, ch, sticky, outside, x - baseX) + } + + function coordsBidiPart(cm, lineObj, lineNo$$1, preparedMeasure, order, x, y) { + // Bidi parts are sorted left-to-right, and in a non-line-wrapping + // situation, we can take this ordering to correspond to the visual + // ordering. This finds the first part whose end is after the given + // coordinates. + var index = findFirst(function (i) { + var part = order[i], ltr = part.level != 1; + return boxIsAfter(cursorCoords(cm, Pos(lineNo$$1, ltr ? part.to : part.from, ltr ? "before" : "after"), + "line", lineObj, preparedMeasure), x, y, true) + }, 0, order.length - 1); + var part = order[index]; + // If this isn't the first part, the part's start is also after + // the coordinates, and the coordinates aren't on the same line as + // that start, move one part back. + if (index > 0) { + var ltr = part.level != 1; + var start = cursorCoords(cm, Pos(lineNo$$1, ltr ? part.from : part.to, ltr ? "after" : "before"), + "line", lineObj, preparedMeasure); + if (boxIsAfter(start, x, y, true) && start.top > y) + { part = order[index - 1]; } + } + return part + } + + function coordsBidiPartWrapped(cm, lineObj, _lineNo, preparedMeasure, order, x, y) { + // In a wrapped line, rtl text on wrapping boundaries can do things + // that don't correspond to the ordering in our `order` array at + // all, so a binary search doesn't work, and we want to return a + // part that only spans one line so that the binary search in + // coordsCharInner is safe. As such, we first find the extent of the + // wrapped line, and then do a flat search in which we discard any + // spans that aren't on the line. + var ref = wrappedLineExtent(cm, lineObj, preparedMeasure, y); + var begin = ref.begin; + var end = ref.end; + if (/\s/.test(lineObj.text.charAt(end - 1))) { end--; } + var part = null, closestDist = null; + for (var i = 0; i < order.length; i++) { + var p = order[i]; + if (p.from >= end || p.to <= begin) { continue } + var ltr = p.level != 1; + var endX = measureCharPrepared(cm, preparedMeasure, ltr ? Math.min(end, p.to) - 1 : Math.max(begin, p.from)).right; + // Weigh against spans ending before this, so that they are only + // picked if nothing ends after + var dist = endX < x ? x - endX + 1e9 : endX - x; + if (!part || closestDist > dist) { + part = p; + closestDist = dist; + } + } + if (!part) { part = order[order.length - 1]; } + // Clip the part to the wrapped line. + if (part.from < begin) { part = {from: begin, to: part.to, level: part.level}; } + if (part.to > end) { part = {from: part.from, to: end, level: part.level}; } + return part + } + + var measureText; + // Compute the default text height. + function textHeight(display) { + if (display.cachedTextHeight != null) { return display.cachedTextHeight } + if (measureText == null) { + measureText = elt("pre"); + // Measure a bunch of lines, for browsers that compute + // fractional heights. + for (var i = 0; i < 49; ++i) { + measureText.appendChild(document.createTextNode("x")); + measureText.appendChild(elt("br")); + } + measureText.appendChild(document.createTextNode("x")); + } + removeChildrenAndAdd(display.measure, measureText); + var height = measureText.offsetHeight / 50; + if (height > 3) { display.cachedTextHeight = height; } + removeChildren(display.measure); + return height || 1 + } + + // Compute the default character width. + function charWidth(display) { + if (display.cachedCharWidth != null) { return display.cachedCharWidth } + var anchor = elt("span", "xxxxxxxxxx"); + var pre = elt("pre", [anchor]); + removeChildrenAndAdd(display.measure, pre); + var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10; + if (width > 2) { display.cachedCharWidth = width; } + return width || 10 + } + + // Do a bulk-read of the DOM positions and sizes needed to draw the + // view, so that we don't interleave reading and writing to the DOM. + function getDimensions(cm) { + var d = cm.display, left = {}, width = {}; + var gutterLeft = d.gutters.clientLeft; + for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { + left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft; + width[cm.options.gutters[i]] = n.clientWidth; + } + return {fixedPos: compensateForHScroll(d), + gutterTotalWidth: d.gutters.offsetWidth, + gutterLeft: left, + gutterWidth: width, + wrapperWidth: d.wrapper.clientWidth} + } + + // Computes display.scroller.scrollLeft + display.gutters.offsetWidth, + // but using getBoundingClientRect to get a sub-pixel-accurate + // result. + function compensateForHScroll(display) { + return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left + } + + // Returns a function that estimates the height of a line, to use as + // first approximation until the line becomes visible (and is thus + // properly measurable). + function estimateHeight(cm) { + var th = textHeight(cm.display), wrapping = cm.options.lineWrapping; + var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3); + return function (line) { + if (lineIsHidden(cm.doc, line)) { return 0 } + + var widgetsHeight = 0; + if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) { + if (line.widgets[i].height) { widgetsHeight += line.widgets[i].height; } + } } + + if (wrapping) + { return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th } + else + { return widgetsHeight + th } + } + } + + function estimateLineHeights(cm) { + var doc = cm.doc, est = estimateHeight(cm); + doc.iter(function (line) { + var estHeight = est(line); + if (estHeight != line.height) { updateLineHeight(line, estHeight); } + }); + } + + // Given a mouse event, find the corresponding position. If liberal + // is false, it checks whether a gutter or scrollbar was clicked, + // and returns null if it was. forRect is used by rectangular + // selections, and tries to estimate a character position even for + // coordinates beyond the right of the text. + function posFromMouse(cm, e, liberal, forRect) { + var display = cm.display; + if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") { return null } + + var x, y, space = display.lineSpace.getBoundingClientRect(); + // Fails unpredictably on IE[67] when mouse is dragged around quickly. + try { x = e.clientX - space.left; y = e.clientY - space.top; } + catch (e) { return null } + var coords = coordsChar(cm, x, y), line; + if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) { + var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length; + coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff)); + } + return coords + } + + // Find the view element corresponding to a given line. Return null + // when the line isn't visible. + function findViewIndex(cm, n) { + if (n >= cm.display.viewTo) { return null } + n -= cm.display.viewFrom; + if (n < 0) { return null } + var view = cm.display.view; + for (var i = 0; i < view.length; i++) { + n -= view[i].size; + if (n < 0) { return i } + } + } + + function updateSelection(cm) { + cm.display.input.showSelection(cm.display.input.prepareSelection()); + } + + function prepareSelection(cm, primary) { + if ( primary === void 0 ) primary = true; + + var doc = cm.doc, result = {}; + var curFragment = result.cursors = document.createDocumentFragment(); + var selFragment = result.selection = document.createDocumentFragment(); + + for (var i = 0; i < doc.sel.ranges.length; i++) { + if (!primary && i == doc.sel.primIndex) { continue } + var range$$1 = doc.sel.ranges[i]; + if (range$$1.from().line >= cm.display.viewTo || range$$1.to().line < cm.display.viewFrom) { continue } + var collapsed = range$$1.empty(); + if (collapsed || cm.options.showCursorWhenSelecting) + { drawSelectionCursor(cm, range$$1.head, curFragment); } + if (!collapsed) + { drawSelectionRange(cm, range$$1, selFragment); } + } + return result + } + + // Draws a cursor for the given range + function drawSelectionCursor(cm, head, output) { + var pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursorHeightPerLine); + + var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor")); + cursor.style.left = pos.left + "px"; + cursor.style.top = pos.top + "px"; + cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px"; + + if (pos.other) { + // Secondary cursor, shown when on a 'jump' in bi-directional text + var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor")); + otherCursor.style.display = ""; + otherCursor.style.left = pos.other.left + "px"; + otherCursor.style.top = pos.other.top + "px"; + otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px"; + } + } + + function cmpCoords(a, b) { return a.top - b.top || a.left - b.left } + + // Draws the given range as a highlighted selection + function drawSelectionRange(cm, range$$1, output) { + var display = cm.display, doc = cm.doc; + var fragment = document.createDocumentFragment(); + var padding = paddingH(cm.display), leftSide = padding.left; + var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right; + var docLTR = doc.direction == "ltr"; + + function add(left, top, width, bottom) { + if (top < 0) { top = 0; } + top = Math.round(top); + bottom = Math.round(bottom); + fragment.appendChild(elt("div", null, "CodeMirror-selected", ("position: absolute; left: " + left + "px;\n top: " + top + "px; width: " + (width == null ? rightSide - left : width) + "px;\n height: " + (bottom - top) + "px"))); + } + + function drawForLine(line, fromArg, toArg) { + var lineObj = getLine(doc, line); + var lineLen = lineObj.text.length; + var start, end; + function coords(ch, bias) { + return charCoords(cm, Pos(line, ch), "div", lineObj, bias) + } + + function wrapX(pos, dir, side) { + var extent = wrappedLineExtentChar(cm, lineObj, null, pos); + var prop = (dir == "ltr") == (side == "after") ? "left" : "right"; + var ch = side == "after" ? extent.begin : extent.end - (/\s/.test(lineObj.text.charAt(extent.end - 1)) ? 2 : 1); + return coords(ch, prop)[prop] + } + + var order = getOrder(lineObj, doc.direction); + iterateBidiSections(order, fromArg || 0, toArg == null ? lineLen : toArg, function (from, to, dir, i) { + var ltr = dir == "ltr"; + var fromPos = coords(from, ltr ? "left" : "right"); + var toPos = coords(to - 1, ltr ? "right" : "left"); + + var openStart = fromArg == null && from == 0, openEnd = toArg == null && to == lineLen; + var first = i == 0, last = !order || i == order.length - 1; + if (toPos.top - fromPos.top <= 3) { // Single line + var openLeft = (docLTR ? openStart : openEnd) && first; + var openRight = (docLTR ? openEnd : openStart) && last; + var left = openLeft ? leftSide : (ltr ? fromPos : toPos).left; + var right = openRight ? rightSide : (ltr ? toPos : fromPos).right; + add(left, fromPos.top, right - left, fromPos.bottom); + } else { // Multiple lines + var topLeft, topRight, botLeft, botRight; + if (ltr) { + topLeft = docLTR && openStart && first ? leftSide : fromPos.left; + topRight = docLTR ? rightSide : wrapX(from, dir, "before"); + botLeft = docLTR ? leftSide : wrapX(to, dir, "after"); + botRight = docLTR && openEnd && last ? rightSide : toPos.right; + } else { + topLeft = !docLTR ? leftSide : wrapX(from, dir, "before"); + topRight = !docLTR && openStart && first ? rightSide : fromPos.right; + botLeft = !docLTR && openEnd && last ? leftSide : toPos.left; + botRight = !docLTR ? rightSide : wrapX(to, dir, "after"); + } + add(topLeft, fromPos.top, topRight - topLeft, fromPos.bottom); + if (fromPos.bottom < toPos.top) { add(leftSide, fromPos.bottom, null, toPos.top); } + add(botLeft, toPos.top, botRight - botLeft, toPos.bottom); + } + + if (!start || cmpCoords(fromPos, start) < 0) { start = fromPos; } + if (cmpCoords(toPos, start) < 0) { start = toPos; } + if (!end || cmpCoords(fromPos, end) < 0) { end = fromPos; } + if (cmpCoords(toPos, end) < 0) { end = toPos; } + }); + return {start: start, end: end} + } + + var sFrom = range$$1.from(), sTo = range$$1.to(); + if (sFrom.line == sTo.line) { + drawForLine(sFrom.line, sFrom.ch, sTo.ch); + } else { + var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line); + var singleVLine = visualLine(fromLine) == visualLine(toLine); + var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end; + var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start; + if (singleVLine) { + if (leftEnd.top < rightStart.top - 2) { + add(leftEnd.right, leftEnd.top, null, leftEnd.bottom); + add(leftSide, rightStart.top, rightStart.left, rightStart.bottom); + } else { + add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom); + } + } + if (leftEnd.bottom < rightStart.top) + { add(leftSide, leftEnd.bottom, null, rightStart.top); } + } + + output.appendChild(fragment); + } + + // Cursor-blinking + function restartBlink(cm) { + if (!cm.state.focused) { return } + var display = cm.display; + clearInterval(display.blinker); + var on = true; + display.cursorDiv.style.visibility = ""; + if (cm.options.cursorBlinkRate > 0) + { display.blinker = setInterval(function () { return display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden"; }, + cm.options.cursorBlinkRate); } + else if (cm.options.cursorBlinkRate < 0) + { display.cursorDiv.style.visibility = "hidden"; } + } + + function ensureFocus(cm) { + if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm); } + } + + function delayBlurEvent(cm) { + cm.state.delayingBlurEvent = true; + setTimeout(function () { if (cm.state.delayingBlurEvent) { + cm.state.delayingBlurEvent = false; + onBlur(cm); + } }, 100); + } + + function onFocus(cm, e) { + if (cm.state.delayingBlurEvent) { cm.state.delayingBlurEvent = false; } + + if (cm.options.readOnly == "nocursor") { return } + if (!cm.state.focused) { + signal(cm, "focus", cm, e); + cm.state.focused = true; + addClass(cm.display.wrapper, "CodeMirror-focused"); + // This test prevents this from firing when a context + // menu is closed (since the input reset would kill the + // select-all detection hack) + if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) { + cm.display.input.reset(); + if (webkit) { setTimeout(function () { return cm.display.input.reset(true); }, 20); } // Issue #1730 + } + cm.display.input.receivedFocus(); + } + restartBlink(cm); + } + function onBlur(cm, e) { + if (cm.state.delayingBlurEvent) { return } + + if (cm.state.focused) { + signal(cm, "blur", cm, e); + cm.state.focused = false; + rmClass(cm.display.wrapper, "CodeMirror-focused"); + } + clearInterval(cm.display.blinker); + setTimeout(function () { if (!cm.state.focused) { cm.display.shift = false; } }, 150); + } + + // Read the actual heights of the rendered lines, and update their + // stored heights to match. + function updateHeightsInViewport(cm) { + var display = cm.display; + var prevBottom = display.lineDiv.offsetTop; + for (var i = 0; i < display.view.length; i++) { + var cur = display.view[i], wrapping = cm.options.lineWrapping; + var height = (void 0), width = 0; + if (cur.hidden) { continue } + if (ie && ie_version < 8) { + var bot = cur.node.offsetTop + cur.node.offsetHeight; + height = bot - prevBottom; + prevBottom = bot; + } else { + var box = cur.node.getBoundingClientRect(); + height = box.bottom - box.top; + // Check that lines don't extend past the right of the current + // editor width + if (!wrapping && cur.text.firstChild) + { width = cur.text.firstChild.getBoundingClientRect().right - box.left - 1; } + } + var diff = cur.line.height - height; + if (diff > .005 || diff < -.005) { + updateLineHeight(cur.line, height); + updateWidgetHeight(cur.line); + if (cur.rest) { for (var j = 0; j < cur.rest.length; j++) + { updateWidgetHeight(cur.rest[j]); } } + } + if (width > cm.display.sizerWidth) { + var chWidth = Math.ceil(width / charWidth(cm.display)); + if (chWidth > cm.display.maxLineLength) { + cm.display.maxLineLength = chWidth; + cm.display.maxLine = cur.line; + cm.display.maxLineChanged = true; + } + } + } + } + + // Read and store the height of line widgets associated with the + // given line. + function updateWidgetHeight(line) { + if (line.widgets) { for (var i = 0; i < line.widgets.length; ++i) { + var w = line.widgets[i], parent = w.node.parentNode; + if (parent) { w.height = parent.offsetHeight; } + } } + } + + // Compute the lines that are visible in a given viewport (defaults + // the the current scroll position). viewport may contain top, + // height, and ensure (see op.scrollToPos) properties. + function visibleLines(display, doc, viewport) { + var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop; + top = Math.floor(top - paddingTop(display)); + var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight; + + var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom); + // Ensure is a {from: {line, ch}, to: {line, ch}} object, and + // forces those lines into the viewport (if possible). + if (viewport && viewport.ensure) { + var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line; + if (ensureFrom < from) { + from = ensureFrom; + to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight); + } else if (Math.min(ensureTo, doc.lastLine()) >= to) { + from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight); + to = ensureTo; + } + } + return {from: from, to: Math.max(to, from + 1)} + } + + // Re-align line numbers and gutter marks to compensate for + // horizontal scrolling. + function alignHorizontally(cm) { + var display = cm.display, view = display.view; + if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) { return } + var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft; + var gutterW = display.gutters.offsetWidth, left = comp + "px"; + for (var i = 0; i < view.length; i++) { if (!view[i].hidden) { + if (cm.options.fixedGutter) { + if (view[i].gutter) + { view[i].gutter.style.left = left; } + if (view[i].gutterBackground) + { view[i].gutterBackground.style.left = left; } + } + var align = view[i].alignable; + if (align) { for (var j = 0; j < align.length; j++) + { align[j].style.left = left; } } + } } + if (cm.options.fixedGutter) + { display.gutters.style.left = (comp + gutterW) + "px"; } + } + + // Used to ensure that the line number gutter is still the right + // size for the current document size. Returns true when an update + // is needed. + function maybeUpdateLineNumberWidth(cm) { + if (!cm.options.lineNumbers) { return false } + var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display; + if (last.length != display.lineNumChars) { + var test = display.measure.appendChild(elt("div", [elt("div", last)], + "CodeMirror-linenumber CodeMirror-gutter-elt")); + var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW; + display.lineGutter.style.width = ""; + display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1; + display.lineNumWidth = display.lineNumInnerWidth + padding; + display.lineNumChars = display.lineNumInnerWidth ? last.length : -1; + display.lineGutter.style.width = display.lineNumWidth + "px"; + updateGutterSpace(cm); + return true + } + return false + } + + // SCROLLING THINGS INTO VIEW + + // If an editor sits on the top or bottom of the window, partially + // scrolled out of view, this ensures that the cursor is visible. + function maybeScrollWindow(cm, rect) { + if (signalDOMEvent(cm, "scrollCursorIntoView")) { return } + + var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null; + if (rect.top + box.top < 0) { doScroll = true; } + else if (rect.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) { doScroll = false; } + if (doScroll != null && !phantom) { + var scrollNode = elt("div", "\u200b", null, ("position: absolute;\n top: " + (rect.top - display.viewOffset - paddingTop(cm.display)) + "px;\n height: " + (rect.bottom - rect.top + scrollGap(cm) + display.barHeight) + "px;\n left: " + (rect.left) + "px; width: " + (Math.max(2, rect.right - rect.left)) + "px;")); + cm.display.lineSpace.appendChild(scrollNode); + scrollNode.scrollIntoView(doScroll); + cm.display.lineSpace.removeChild(scrollNode); + } + } + + // Scroll a given position into view (immediately), verifying that + // it actually became visible (as line heights are accurately + // measured, the position of something may 'drift' during drawing). + function scrollPosIntoView(cm, pos, end, margin) { + if (margin == null) { margin = 0; } + var rect; + if (!cm.options.lineWrapping && pos == end) { + // Set pos and end to the cursor positions around the character pos sticks to + // If pos.sticky == "before", that is around pos.ch - 1, otherwise around pos.ch + // If pos == Pos(_, 0, "before"), pos and end are unchanged + pos = pos.ch ? Pos(pos.line, pos.sticky == "before" ? pos.ch - 1 : pos.ch, "after") : pos; + end = pos.sticky == "before" ? Pos(pos.line, pos.ch + 1, "before") : pos; + } + for (var limit = 0; limit < 5; limit++) { + var changed = false; + var coords = cursorCoords(cm, pos); + var endCoords = !end || end == pos ? coords : cursorCoords(cm, end); + rect = {left: Math.min(coords.left, endCoords.left), + top: Math.min(coords.top, endCoords.top) - margin, + right: Math.max(coords.left, endCoords.left), + bottom: Math.max(coords.bottom, endCoords.bottom) + margin}; + var scrollPos = calculateScrollPos(cm, rect); + var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft; + if (scrollPos.scrollTop != null) { + updateScrollTop(cm, scrollPos.scrollTop); + if (Math.abs(cm.doc.scrollTop - startTop) > 1) { changed = true; } + } + if (scrollPos.scrollLeft != null) { + setScrollLeft(cm, scrollPos.scrollLeft); + if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) { changed = true; } + } + if (!changed) { break } + } + return rect + } + + // Scroll a given set of coordinates into view (immediately). + function scrollIntoView(cm, rect) { + var scrollPos = calculateScrollPos(cm, rect); + if (scrollPos.scrollTop != null) { updateScrollTop(cm, scrollPos.scrollTop); } + if (scrollPos.scrollLeft != null) { setScrollLeft(cm, scrollPos.scrollLeft); } + } + + // Calculate a new scroll position needed to scroll the given + // rectangle into view. Returns an object with scrollTop and + // scrollLeft properties. When these are undefined, the + // vertical/horizontal position does not need to be adjusted. + function calculateScrollPos(cm, rect) { + var display = cm.display, snapMargin = textHeight(cm.display); + if (rect.top < 0) { rect.top = 0; } + var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop; + var screen = displayHeight(cm), result = {}; + if (rect.bottom - rect.top > screen) { rect.bottom = rect.top + screen; } + var docBottom = cm.doc.height + paddingVert(display); + var atTop = rect.top < snapMargin, atBottom = rect.bottom > docBottom - snapMargin; + if (rect.top < screentop) { + result.scrollTop = atTop ? 0 : rect.top; + } else if (rect.bottom > screentop + screen) { + var newTop = Math.min(rect.top, (atBottom ? docBottom : rect.bottom) - screen); + if (newTop != screentop) { result.scrollTop = newTop; } + } + + var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft; + var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0); + var tooWide = rect.right - rect.left > screenw; + if (tooWide) { rect.right = rect.left + screenw; } + if (rect.left < 10) + { result.scrollLeft = 0; } + else if (rect.left < screenleft) + { result.scrollLeft = Math.max(0, rect.left - (tooWide ? 0 : 10)); } + else if (rect.right > screenw + screenleft - 3) + { result.scrollLeft = rect.right + (tooWide ? 0 : 10) - screenw; } + return result + } + + // Store a relative adjustment to the scroll position in the current + // operation (to be applied when the operation finishes). + function addToScrollTop(cm, top) { + if (top == null) { return } + resolveScrollToPos(cm); + cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top; + } + + // Make sure that at the end of the operation the current cursor is + // shown. + function ensureCursorVisible(cm) { + resolveScrollToPos(cm); + var cur = cm.getCursor(); + cm.curOp.scrollToPos = {from: cur, to: cur, margin: cm.options.cursorScrollMargin}; + } + + function scrollToCoords(cm, x, y) { + if (x != null || y != null) { resolveScrollToPos(cm); } + if (x != null) { cm.curOp.scrollLeft = x; } + if (y != null) { cm.curOp.scrollTop = y; } + } + + function scrollToRange(cm, range$$1) { + resolveScrollToPos(cm); + cm.curOp.scrollToPos = range$$1; + } + + // When an operation has its scrollToPos property set, and another + // scroll action is applied before the end of the operation, this + // 'simulates' scrolling that position into view in a cheap way, so + // that the effect of intermediate scroll commands is not ignored. + function resolveScrollToPos(cm) { + var range$$1 = cm.curOp.scrollToPos; + if (range$$1) { + cm.curOp.scrollToPos = null; + var from = estimateCoords(cm, range$$1.from), to = estimateCoords(cm, range$$1.to); + scrollToCoordsRange(cm, from, to, range$$1.margin); + } + } + + function scrollToCoordsRange(cm, from, to, margin) { + var sPos = calculateScrollPos(cm, { + left: Math.min(from.left, to.left), + top: Math.min(from.top, to.top) - margin, + right: Math.max(from.right, to.right), + bottom: Math.max(from.bottom, to.bottom) + margin + }); + scrollToCoords(cm, sPos.scrollLeft, sPos.scrollTop); + } + + // Sync the scrollable area and scrollbars, ensure the viewport + // covers the visible area. + function updateScrollTop(cm, val) { + if (Math.abs(cm.doc.scrollTop - val) < 2) { return } + if (!gecko) { updateDisplaySimple(cm, {top: val}); } + setScrollTop(cm, val, true); + if (gecko) { updateDisplaySimple(cm); } + startWorker(cm, 100); + } + + function setScrollTop(cm, val, forceScroll) { + val = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight, val); + if (cm.display.scroller.scrollTop == val && !forceScroll) { return } + cm.doc.scrollTop = val; + cm.display.scrollbars.setScrollTop(val); + if (cm.display.scroller.scrollTop != val) { cm.display.scroller.scrollTop = val; } + } + + // Sync scroller and scrollbar, ensure the gutter elements are + // aligned. + function setScrollLeft(cm, val, isScroller, forceScroll) { + val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth); + if ((isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) && !forceScroll) { return } + cm.doc.scrollLeft = val; + alignHorizontally(cm); + if (cm.display.scroller.scrollLeft != val) { cm.display.scroller.scrollLeft = val; } + cm.display.scrollbars.setScrollLeft(val); + } + + // SCROLLBARS + + // Prepare DOM reads needed to update the scrollbars. Done in one + // shot to minimize update/measure roundtrips. + function measureForScrollbars(cm) { + var d = cm.display, gutterW = d.gutters.offsetWidth; + var docH = Math.round(cm.doc.height + paddingVert(cm.display)); + return { + clientHeight: d.scroller.clientHeight, + viewHeight: d.wrapper.clientHeight, + scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth, + viewWidth: d.wrapper.clientWidth, + barLeft: cm.options.fixedGutter ? gutterW : 0, + docHeight: docH, + scrollHeight: docH + scrollGap(cm) + d.barHeight, + nativeBarWidth: d.nativeBarWidth, + gutterWidth: gutterW + } + } + + var NativeScrollbars = function(place, scroll, cm) { + this.cm = cm; + var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar"); + var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar"); + vert.tabIndex = horiz.tabIndex = -1; + place(vert); place(horiz); + + on(vert, "scroll", function () { + if (vert.clientHeight) { scroll(vert.scrollTop, "vertical"); } + }); + on(horiz, "scroll", function () { + if (horiz.clientWidth) { scroll(horiz.scrollLeft, "horizontal"); } + }); + + this.checkedZeroWidth = false; + // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). + if (ie && ie_version < 8) { this.horiz.style.minHeight = this.vert.style.minWidth = "18px"; } + }; + + NativeScrollbars.prototype.update = function (measure) { + var needsH = measure.scrollWidth > measure.clientWidth + 1; + var needsV = measure.scrollHeight > measure.clientHeight + 1; + var sWidth = measure.nativeBarWidth; + + if (needsV) { + this.vert.style.display = "block"; + this.vert.style.bottom = needsH ? sWidth + "px" : "0"; + var totalHeight = measure.viewHeight - (needsH ? sWidth : 0); + // A bug in IE8 can cause this value to be negative, so guard it. + this.vert.firstChild.style.height = + Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px"; + } else { + this.vert.style.display = ""; + this.vert.firstChild.style.height = "0"; + } + + if (needsH) { + this.horiz.style.display = "block"; + this.horiz.style.right = needsV ? sWidth + "px" : "0"; + this.horiz.style.left = measure.barLeft + "px"; + var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0); + this.horiz.firstChild.style.width = + Math.max(0, measure.scrollWidth - measure.clientWidth + totalWidth) + "px"; + } else { + this.horiz.style.display = ""; + this.horiz.firstChild.style.width = "0"; + } + + if (!this.checkedZeroWidth && measure.clientHeight > 0) { + if (sWidth == 0) { this.zeroWidthHack(); } + this.checkedZeroWidth = true; + } + + return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0} + }; + + NativeScrollbars.prototype.setScrollLeft = function (pos) { + if (this.horiz.scrollLeft != pos) { this.horiz.scrollLeft = pos; } + if (this.disableHoriz) { this.enableZeroWidthBar(this.horiz, this.disableHoriz, "horiz"); } + }; + + NativeScrollbars.prototype.setScrollTop = function (pos) { + if (this.vert.scrollTop != pos) { this.vert.scrollTop = pos; } + if (this.disableVert) { this.enableZeroWidthBar(this.vert, this.disableVert, "vert"); } + }; + + NativeScrollbars.prototype.zeroWidthHack = function () { + var w = mac && !mac_geMountainLion ? "12px" : "18px"; + this.horiz.style.height = this.vert.style.width = w; + this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none"; + this.disableHoriz = new Delayed; + this.disableVert = new Delayed; + }; + + NativeScrollbars.prototype.enableZeroWidthBar = function (bar, delay, type) { + bar.style.pointerEvents = "auto"; + function maybeDisable() { + // To find out whether the scrollbar is still visible, we + // check whether the element under the pixel in the bottom + // right corner of the scrollbar box is the scrollbar box + // itself (when the bar is still visible) or its filler child + // (when the bar is hidden). If it is still visible, we keep + // it enabled, if it's hidden, we disable pointer events. + var box = bar.getBoundingClientRect(); + var elt$$1 = type == "vert" ? document.elementFromPoint(box.right - 1, (box.top + box.bottom) / 2) + : document.elementFromPoint((box.right + box.left) / 2, box.bottom - 1); + if (elt$$1 != bar) { bar.style.pointerEvents = "none"; } + else { delay.set(1000, maybeDisable); } + } + delay.set(1000, maybeDisable); + }; + + NativeScrollbars.prototype.clear = function () { + var parent = this.horiz.parentNode; + parent.removeChild(this.horiz); + parent.removeChild(this.vert); + }; + + var NullScrollbars = function () {}; + + NullScrollbars.prototype.update = function () { return {bottom: 0, right: 0} }; + NullScrollbars.prototype.setScrollLeft = function () {}; + NullScrollbars.prototype.setScrollTop = function () {}; + NullScrollbars.prototype.clear = function () {}; + + function updateScrollbars(cm, measure) { + if (!measure) { measure = measureForScrollbars(cm); } + var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight; + updateScrollbarsInner(cm, measure); + for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) { + if (startWidth != cm.display.barWidth && cm.options.lineWrapping) + { updateHeightsInViewport(cm); } + updateScrollbarsInner(cm, measureForScrollbars(cm)); + startWidth = cm.display.barWidth; startHeight = cm.display.barHeight; + } + } + + // Re-synchronize the fake scrollbars with the actual size of the + // content. + function updateScrollbarsInner(cm, measure) { + var d = cm.display; + var sizes = d.scrollbars.update(measure); + + d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px"; + d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px"; + d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent"; + + if (sizes.right && sizes.bottom) { + d.scrollbarFiller.style.display = "block"; + d.scrollbarFiller.style.height = sizes.bottom + "px"; + d.scrollbarFiller.style.width = sizes.right + "px"; + } else { d.scrollbarFiller.style.display = ""; } + if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { + d.gutterFiller.style.display = "block"; + d.gutterFiller.style.height = sizes.bottom + "px"; + d.gutterFiller.style.width = measure.gutterWidth + "px"; + } else { d.gutterFiller.style.display = ""; } + } + + var scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars}; + + function initScrollbars(cm) { + if (cm.display.scrollbars) { + cm.display.scrollbars.clear(); + if (cm.display.scrollbars.addClass) + { rmClass(cm.display.wrapper, cm.display.scrollbars.addClass); } + } + + cm.display.scrollbars = new scrollbarModel[cm.options.scrollbarStyle](function (node) { + cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller); + // Prevent clicks in the scrollbars from killing focus + on(node, "mousedown", function () { + if (cm.state.focused) { setTimeout(function () { return cm.display.input.focus(); }, 0); } + }); + node.setAttribute("cm-not-content", "true"); + }, function (pos, axis) { + if (axis == "horizontal") { setScrollLeft(cm, pos); } + else { updateScrollTop(cm, pos); } + }, cm); + if (cm.display.scrollbars.addClass) + { addClass(cm.display.wrapper, cm.display.scrollbars.addClass); } + } + + // Operations are used to wrap a series of changes to the editor + // state in such a way that each change won't have to update the + // cursor and display (which would be awkward, slow, and + // error-prone). Instead, display updates are batched and then all + // combined and executed at once. + + var nextOpId = 0; + // Start a new operation. + function startOperation(cm) { + cm.curOp = { + cm: cm, + viewChanged: false, // Flag that indicates that lines might need to be redrawn + startHeight: cm.doc.height, // Used to detect need to update scrollbar + forceUpdate: false, // Used to force a redraw + updateInput: 0, // Whether to reset the input textarea + typing: false, // Whether this reset should be careful to leave existing text (for compositing) + changeObjs: null, // Accumulated changes, for firing change events + cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on + cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already + selectionChanged: false, // Whether the selection needs to be redrawn + updateMaxLine: false, // Set when the widest line needs to be determined anew + scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet + scrollToPos: null, // Used to scroll to a specific position + focus: false, + id: ++nextOpId // Unique ID + }; + pushOperation(cm.curOp); + } + + // Finish an operation, updating the display and signalling delayed events + function endOperation(cm) { + var op = cm.curOp; + if (op) { finishOperation(op, function (group) { + for (var i = 0; i < group.ops.length; i++) + { group.ops[i].cm.curOp = null; } + endOperations(group); + }); } + } + + // The DOM updates done when an operation finishes are batched so + // that the minimum number of relayouts are required. + function endOperations(group) { + var ops = group.ops; + for (var i = 0; i < ops.length; i++) // Read DOM + { endOperation_R1(ops[i]); } + for (var i$1 = 0; i$1 < ops.length; i$1++) // Write DOM (maybe) + { endOperation_W1(ops[i$1]); } + for (var i$2 = 0; i$2 < ops.length; i$2++) // Read DOM + { endOperation_R2(ops[i$2]); } + for (var i$3 = 0; i$3 < ops.length; i$3++) // Write DOM (maybe) + { endOperation_W2(ops[i$3]); } + for (var i$4 = 0; i$4 < ops.length; i$4++) // Read DOM + { endOperation_finish(ops[i$4]); } + } + + function endOperation_R1(op) { + var cm = op.cm, display = cm.display; + maybeClipScrollbars(cm); + if (op.updateMaxLine) { findMaxLine(cm); } + + op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null || + op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom || + op.scrollToPos.to.line >= display.viewTo) || + display.maxLineChanged && cm.options.lineWrapping; + op.update = op.mustUpdate && + new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate); + } + + function endOperation_W1(op) { + op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update); + } + + function endOperation_R2(op) { + var cm = op.cm, display = cm.display; + if (op.updatedDisplay) { updateHeightsInViewport(cm); } + + op.barMeasure = measureForScrollbars(cm); + + // If the max line changed since it was last measured, measure it, + // and ensure the document's width matches it. + // updateDisplay_W2 will use these properties to do the actual resizing + if (display.maxLineChanged && !cm.options.lineWrapping) { + op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3; + cm.display.sizerWidth = op.adjustWidthTo; + op.barMeasure.scrollWidth = + Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth); + op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm)); + } + + if (op.updatedDisplay || op.selectionChanged) + { op.preparedSelection = display.input.prepareSelection(); } + } + + function endOperation_W2(op) { + var cm = op.cm; + + if (op.adjustWidthTo != null) { + cm.display.sizer.style.minWidth = op.adjustWidthTo + "px"; + if (op.maxScrollLeft < cm.doc.scrollLeft) + { setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true); } + cm.display.maxLineChanged = false; + } + + var takeFocus = op.focus && op.focus == activeElt(); + if (op.preparedSelection) + { cm.display.input.showSelection(op.preparedSelection, takeFocus); } + if (op.updatedDisplay || op.startHeight != cm.doc.height) + { updateScrollbars(cm, op.barMeasure); } + if (op.updatedDisplay) + { setDocumentHeight(cm, op.barMeasure); } + + if (op.selectionChanged) { restartBlink(cm); } + + if (cm.state.focused && op.updateInput) + { cm.display.input.reset(op.typing); } + if (takeFocus) { ensureFocus(op.cm); } + } + + function endOperation_finish(op) { + var cm = op.cm, display = cm.display, doc = cm.doc; + + if (op.updatedDisplay) { postUpdateDisplay(cm, op.update); } + + // Abort mouse wheel delta measurement, when scrolling explicitly + if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos)) + { display.wheelStartX = display.wheelStartY = null; } + + // Propagate the scroll position to the actual DOM scroller + if (op.scrollTop != null) { setScrollTop(cm, op.scrollTop, op.forceScroll); } + + if (op.scrollLeft != null) { setScrollLeft(cm, op.scrollLeft, true, true); } + // If we need to scroll a specific position into view, do so. + if (op.scrollToPos) { + var rect = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), + clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin); + maybeScrollWindow(cm, rect); + } + + // Fire events for markers that are hidden/unidden by editing or + // undoing + var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers; + if (hidden) { for (var i = 0; i < hidden.length; ++i) + { if (!hidden[i].lines.length) { signal(hidden[i], "hide"); } } } + if (unhidden) { for (var i$1 = 0; i$1 < unhidden.length; ++i$1) + { if (unhidden[i$1].lines.length) { signal(unhidden[i$1], "unhide"); } } } + + if (display.wrapper.offsetHeight) + { doc.scrollTop = cm.display.scroller.scrollTop; } + + // Fire change events, and delayed event handlers + if (op.changeObjs) + { signal(cm, "changes", cm, op.changeObjs); } + if (op.update) + { op.update.finish(); } + } + + // Run the given function in an operation + function runInOp(cm, f) { + if (cm.curOp) { return f() } + startOperation(cm); + try { return f() } + finally { endOperation(cm); } + } + // Wraps a function in an operation. Returns the wrapped function. + function operation(cm, f) { + return function() { + if (cm.curOp) { return f.apply(cm, arguments) } + startOperation(cm); + try { return f.apply(cm, arguments) } + finally { endOperation(cm); } + } + } + // Used to add methods to editor and doc instances, wrapping them in + // operations. + function methodOp(f) { + return function() { + if (this.curOp) { return f.apply(this, arguments) } + startOperation(this); + try { return f.apply(this, arguments) } + finally { endOperation(this); } + } + } + function docMethodOp(f) { + return function() { + var cm = this.cm; + if (!cm || cm.curOp) { return f.apply(this, arguments) } + startOperation(cm); + try { return f.apply(this, arguments) } + finally { endOperation(cm); } + } + } + + // Updates the display.view data structure for a given change to the + // document. From and to are in pre-change coordinates. Lendiff is + // the amount of lines added or subtracted by the change. This is + // used for changes that span multiple lines, or change the way + // lines are divided into visual lines. regLineChange (below) + // registers single-line changes. + function regChange(cm, from, to, lendiff) { + if (from == null) { from = cm.doc.first; } + if (to == null) { to = cm.doc.first + cm.doc.size; } + if (!lendiff) { lendiff = 0; } + + var display = cm.display; + if (lendiff && to < display.viewTo && + (display.updateLineNumbers == null || display.updateLineNumbers > from)) + { display.updateLineNumbers = from; } + + cm.curOp.viewChanged = true; + + if (from >= display.viewTo) { // Change after + if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo) + { resetView(cm); } + } else if (to <= display.viewFrom) { // Change before + if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) { + resetView(cm); + } else { + display.viewFrom += lendiff; + display.viewTo += lendiff; + } + } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap + resetView(cm); + } else if (from <= display.viewFrom) { // Top overlap + var cut = viewCuttingPoint(cm, to, to + lendiff, 1); + if (cut) { + display.view = display.view.slice(cut.index); + display.viewFrom = cut.lineN; + display.viewTo += lendiff; + } else { + resetView(cm); + } + } else if (to >= display.viewTo) { // Bottom overlap + var cut$1 = viewCuttingPoint(cm, from, from, -1); + if (cut$1) { + display.view = display.view.slice(0, cut$1.index); + display.viewTo = cut$1.lineN; + } else { + resetView(cm); + } + } else { // Gap in the middle + var cutTop = viewCuttingPoint(cm, from, from, -1); + var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1); + if (cutTop && cutBot) { + display.view = display.view.slice(0, cutTop.index) + .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN)) + .concat(display.view.slice(cutBot.index)); + display.viewTo += lendiff; + } else { + resetView(cm); + } + } + + var ext = display.externalMeasured; + if (ext) { + if (to < ext.lineN) + { ext.lineN += lendiff; } + else if (from < ext.lineN + ext.size) + { display.externalMeasured = null; } + } + } + + // Register a change to a single line. Type must be one of "text", + // "gutter", "class", "widget" + function regLineChange(cm, line, type) { + cm.curOp.viewChanged = true; + var display = cm.display, ext = cm.display.externalMeasured; + if (ext && line >= ext.lineN && line < ext.lineN + ext.size) + { display.externalMeasured = null; } + + if (line < display.viewFrom || line >= display.viewTo) { return } + var lineView = display.view[findViewIndex(cm, line)]; + if (lineView.node == null) { return } + var arr = lineView.changes || (lineView.changes = []); + if (indexOf(arr, type) == -1) { arr.push(type); } + } + + // Clear the view. + function resetView(cm) { + cm.display.viewFrom = cm.display.viewTo = cm.doc.first; + cm.display.view = []; + cm.display.viewOffset = 0; + } + + function viewCuttingPoint(cm, oldN, newN, dir) { + var index = findViewIndex(cm, oldN), diff, view = cm.display.view; + if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size) + { return {index: index, lineN: newN} } + var n = cm.display.viewFrom; + for (var i = 0; i < index; i++) + { n += view[i].size; } + if (n != oldN) { + if (dir > 0) { + if (index == view.length - 1) { return null } + diff = (n + view[index].size) - oldN; + index++; + } else { + diff = n - oldN; + } + oldN += diff; newN += diff; + } + while (visualLineNo(cm.doc, newN) != newN) { + if (index == (dir < 0 ? 0 : view.length - 1)) { return null } + newN += dir * view[index - (dir < 0 ? 1 : 0)].size; + index += dir; + } + return {index: index, lineN: newN} + } + + // Force the view to cover a given range, adding empty view element + // or clipping off existing ones as needed. + function adjustView(cm, from, to) { + var display = cm.display, view = display.view; + if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) { + display.view = buildViewArray(cm, from, to); + display.viewFrom = from; + } else { + if (display.viewFrom > from) + { display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view); } + else if (display.viewFrom < from) + { display.view = display.view.slice(findViewIndex(cm, from)); } + display.viewFrom = from; + if (display.viewTo < to) + { display.view = display.view.concat(buildViewArray(cm, display.viewTo, to)); } + else if (display.viewTo > to) + { display.view = display.view.slice(0, findViewIndex(cm, to)); } + } + display.viewTo = to; + } + + // Count the number of lines in the view whose DOM representation is + // out of date (or nonexistent). + function countDirtyView(cm) { + var view = cm.display.view, dirty = 0; + for (var i = 0; i < view.length; i++) { + var lineView = view[i]; + if (!lineView.hidden && (!lineView.node || lineView.changes)) { ++dirty; } + } + return dirty + } + + // HIGHLIGHT WORKER + + function startWorker(cm, time) { + if (cm.doc.highlightFrontier < cm.display.viewTo) + { cm.state.highlight.set(time, bind(highlightWorker, cm)); } + } + + function highlightWorker(cm) { + var doc = cm.doc; + if (doc.highlightFrontier >= cm.display.viewTo) { return } + var end = +new Date + cm.options.workTime; + var context = getContextBefore(cm, doc.highlightFrontier); + var changedLines = []; + + doc.iter(context.line, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function (line) { + if (context.line >= cm.display.viewFrom) { // Visible + var oldStyles = line.styles; + var resetState = line.text.length > cm.options.maxHighlightLength ? copyState(doc.mode, context.state) : null; + var highlighted = highlightLine(cm, line, context, true); + if (resetState) { context.state = resetState; } + line.styles = highlighted.styles; + var oldCls = line.styleClasses, newCls = highlighted.classes; + if (newCls) { line.styleClasses = newCls; } + else if (oldCls) { line.styleClasses = null; } + var ischange = !oldStyles || oldStyles.length != line.styles.length || + oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass); + for (var i = 0; !ischange && i < oldStyles.length; ++i) { ischange = oldStyles[i] != line.styles[i]; } + if (ischange) { changedLines.push(context.line); } + line.stateAfter = context.save(); + context.nextLine(); + } else { + if (line.text.length <= cm.options.maxHighlightLength) + { processLine(cm, line.text, context); } + line.stateAfter = context.line % 5 == 0 ? context.save() : null; + context.nextLine(); + } + if (+new Date > end) { + startWorker(cm, cm.options.workDelay); + return true + } + }); + doc.highlightFrontier = context.line; + doc.modeFrontier = Math.max(doc.modeFrontier, context.line); + if (changedLines.length) { runInOp(cm, function () { + for (var i = 0; i < changedLines.length; i++) + { regLineChange(cm, changedLines[i], "text"); } + }); } + } + + // DISPLAY DRAWING + + var DisplayUpdate = function(cm, viewport, force) { + var display = cm.display; + + this.viewport = viewport; + // Store some values that we'll need later (but don't want to force a relayout for) + this.visible = visibleLines(display, cm.doc, viewport); + this.editorIsHidden = !display.wrapper.offsetWidth; + this.wrapperHeight = display.wrapper.clientHeight; + this.wrapperWidth = display.wrapper.clientWidth; + this.oldDisplayWidth = displayWidth(cm); + this.force = force; + this.dims = getDimensions(cm); + this.events = []; + }; + + DisplayUpdate.prototype.signal = function (emitter, type) { + if (hasHandler(emitter, type)) + { this.events.push(arguments); } + }; + DisplayUpdate.prototype.finish = function () { + var this$1 = this; + + for (var i = 0; i < this.events.length; i++) + { signal.apply(null, this$1.events[i]); } + }; + + function maybeClipScrollbars(cm) { + var display = cm.display; + if (!display.scrollbarsClipped && display.scroller.offsetWidth) { + display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth; + display.heightForcer.style.height = scrollGap(cm) + "px"; + display.sizer.style.marginBottom = -display.nativeBarWidth + "px"; + display.sizer.style.borderRightWidth = scrollGap(cm) + "px"; + display.scrollbarsClipped = true; + } + } + + function selectionSnapshot(cm) { + if (cm.hasFocus()) { return null } + var active = activeElt(); + if (!active || !contains(cm.display.lineDiv, active)) { return null } + var result = {activeElt: active}; + if (window.getSelection) { + var sel = window.getSelection(); + if (sel.anchorNode && sel.extend && contains(cm.display.lineDiv, sel.anchorNode)) { + result.anchorNode = sel.anchorNode; + result.anchorOffset = sel.anchorOffset; + result.focusNode = sel.focusNode; + result.focusOffset = sel.focusOffset; + } + } + return result + } + + function restoreSelection(snapshot) { + if (!snapshot || !snapshot.activeElt || snapshot.activeElt == activeElt()) { return } + snapshot.activeElt.focus(); + if (snapshot.anchorNode && contains(document.body, snapshot.anchorNode) && contains(document.body, snapshot.focusNode)) { + var sel = window.getSelection(), range$$1 = document.createRange(); + range$$1.setEnd(snapshot.anchorNode, snapshot.anchorOffset); + range$$1.collapse(false); + sel.removeAllRanges(); + sel.addRange(range$$1); + sel.extend(snapshot.focusNode, snapshot.focusOffset); + } + } + + // Does the actual updating of the line display. Bails out + // (returning false) when there is nothing to be done and forced is + // false. + function updateDisplayIfNeeded(cm, update) { + var display = cm.display, doc = cm.doc; + + if (update.editorIsHidden) { + resetView(cm); + return false + } + + // Bail out if the visible area is already rendered and nothing changed. + if (!update.force && + update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo && + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) && + display.renderedView == display.view && countDirtyView(cm) == 0) + { return false } + + if (maybeUpdateLineNumberWidth(cm)) { + resetView(cm); + update.dims = getDimensions(cm); + } + + // Compute a suitable new viewport (from & to) + var end = doc.first + doc.size; + var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first); + var to = Math.min(end, update.visible.to + cm.options.viewportMargin); + if (display.viewFrom < from && from - display.viewFrom < 20) { from = Math.max(doc.first, display.viewFrom); } + if (display.viewTo > to && display.viewTo - to < 20) { to = Math.min(end, display.viewTo); } + if (sawCollapsedSpans) { + from = visualLineNo(cm.doc, from); + to = visualLineEndNo(cm.doc, to); + } + + var different = from != display.viewFrom || to != display.viewTo || + display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth; + adjustView(cm, from, to); + + display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom)); + // Position the mover div to align with the current scroll position + cm.display.mover.style.top = display.viewOffset + "px"; + + var toUpdate = countDirtyView(cm); + if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view && + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo)) + { return false } + + // For big changes, we hide the enclosing element during the + // update, since that speeds up the operations on most browsers. + var selSnapshot = selectionSnapshot(cm); + if (toUpdate > 4) { display.lineDiv.style.display = "none"; } + patchDisplay(cm, display.updateLineNumbers, update.dims); + if (toUpdate > 4) { display.lineDiv.style.display = ""; } + display.renderedView = display.view; + // There might have been a widget with a focused element that got + // hidden or updated, if so re-focus it. + restoreSelection(selSnapshot); + + // Prevent selection and cursors from interfering with the scroll + // width and height. + removeChildren(display.cursorDiv); + removeChildren(display.selectionDiv); + display.gutters.style.height = display.sizer.style.minHeight = 0; + + if (different) { + display.lastWrapHeight = update.wrapperHeight; + display.lastWrapWidth = update.wrapperWidth; + startWorker(cm, 400); + } + + display.updateLineNumbers = null; + + return true + } + + function postUpdateDisplay(cm, update) { + var viewport = update.viewport; + + for (var first = true;; first = false) { + if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) { + // Clip forced viewport to actual scrollable area. + if (viewport && viewport.top != null) + { viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)}; } + // Updated line heights might result in the drawn area not + // actually covering the viewport. Keep looping until it does. + update.visible = visibleLines(cm.display, cm.doc, viewport); + if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo) + { break } + } + if (!updateDisplayIfNeeded(cm, update)) { break } + updateHeightsInViewport(cm); + var barMeasure = measureForScrollbars(cm); + updateSelection(cm); + updateScrollbars(cm, barMeasure); + setDocumentHeight(cm, barMeasure); + update.force = false; + } + + update.signal(cm, "update", cm); + if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) { + update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo); + cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo; + } + } + + function updateDisplaySimple(cm, viewport) { + var update = new DisplayUpdate(cm, viewport); + if (updateDisplayIfNeeded(cm, update)) { + updateHeightsInViewport(cm); + postUpdateDisplay(cm, update); + var barMeasure = measureForScrollbars(cm); + updateSelection(cm); + updateScrollbars(cm, barMeasure); + setDocumentHeight(cm, barMeasure); + update.finish(); + } + } + + // Sync the actual display DOM structure with display.view, removing + // nodes for lines that are no longer in view, and creating the ones + // that are not there yet, and updating the ones that are out of + // date. + function patchDisplay(cm, updateNumbersFrom, dims) { + var display = cm.display, lineNumbers = cm.options.lineNumbers; + var container = display.lineDiv, cur = container.firstChild; + + function rm(node) { + var next = node.nextSibling; + // Works around a throw-scroll bug in OS X Webkit + if (webkit && mac && cm.display.currentWheelTarget == node) + { node.style.display = "none"; } + else + { node.parentNode.removeChild(node); } + return next + } + + var view = display.view, lineN = display.viewFrom; + // Loop over the elements in the view, syncing cur (the DOM nodes + // in display.lineDiv) with the view as we go. + for (var i = 0; i < view.length; i++) { + var lineView = view[i]; + if (lineView.hidden) ; else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet + var node = buildLineElement(cm, lineView, lineN, dims); + container.insertBefore(node, cur); + } else { // Already drawn + while (cur != lineView.node) { cur = rm(cur); } + var updateNumber = lineNumbers && updateNumbersFrom != null && + updateNumbersFrom <= lineN && lineView.lineNumber; + if (lineView.changes) { + if (indexOf(lineView.changes, "gutter") > -1) { updateNumber = false; } + updateLineForChanges(cm, lineView, lineN, dims); + } + if (updateNumber) { + removeChildren(lineView.lineNumber); + lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN))); + } + cur = lineView.node.nextSibling; + } + lineN += lineView.size; + } + while (cur) { cur = rm(cur); } + } + + function updateGutterSpace(cm) { + var width = cm.display.gutters.offsetWidth; + cm.display.sizer.style.marginLeft = width + "px"; + } + + function setDocumentHeight(cm, measure) { + cm.display.sizer.style.minHeight = measure.docHeight + "px"; + cm.display.heightForcer.style.top = measure.docHeight + "px"; + cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + "px"; + } + + // Rebuild the gutter elements, ensure the margin to the left of the + // code matches their width. + function updateGutters(cm) { + var gutters = cm.display.gutters, specs = cm.options.gutters; + removeChildren(gutters); + var i = 0; + for (; i < specs.length; ++i) { + var gutterClass = specs[i]; + var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass)); + if (gutterClass == "CodeMirror-linenumbers") { + cm.display.lineGutter = gElt; + gElt.style.width = (cm.display.lineNumWidth || 1) + "px"; + } + } + gutters.style.display = i ? "" : "none"; + updateGutterSpace(cm); + } + + // Make sure the gutters options contains the element + // "CodeMirror-linenumbers" when the lineNumbers option is true. + function setGuttersForLineNumbers(options) { + var found = indexOf(options.gutters, "CodeMirror-linenumbers"); + if (found == -1 && options.lineNumbers) { + options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]); + } else if (found > -1 && !options.lineNumbers) { + options.gutters = options.gutters.slice(0); + options.gutters.splice(found, 1); + } + } + + // Since the delta values reported on mouse wheel events are + // unstandardized between browsers and even browser versions, and + // generally horribly unpredictable, this code starts by measuring + // the scroll effect that the first few mouse wheel events have, + // and, from that, detects the way it can convert deltas to pixel + // offsets afterwards. + // + // The reason we want to know the amount a wheel event will scroll + // is that it gives us a chance to update the display before the + // actual scrolling happens, reducing flickering. + + var wheelSamples = 0, wheelPixelsPerUnit = null; + // Fill in a browser-detected starting value on browsers where we + // know one. These don't have to be accurate -- the result of them + // being wrong would just be a slight flicker on the first wheel + // scroll (if it is large enough). + if (ie) { wheelPixelsPerUnit = -.53; } + else if (gecko) { wheelPixelsPerUnit = 15; } + else if (chrome) { wheelPixelsPerUnit = -.7; } + else if (safari) { wheelPixelsPerUnit = -1/3; } + + function wheelEventDelta(e) { + var dx = e.wheelDeltaX, dy = e.wheelDeltaY; + if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) { dx = e.detail; } + if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) { dy = e.detail; } + else if (dy == null) { dy = e.wheelDelta; } + return {x: dx, y: dy} + } + function wheelEventPixels(e) { + var delta = wheelEventDelta(e); + delta.x *= wheelPixelsPerUnit; + delta.y *= wheelPixelsPerUnit; + return delta + } + + function onScrollWheel(cm, e) { + var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y; + + var display = cm.display, scroll = display.scroller; + // Quit if there's nothing to scroll here + var canScrollX = scroll.scrollWidth > scroll.clientWidth; + var canScrollY = scroll.scrollHeight > scroll.clientHeight; + if (!(dx && canScrollX || dy && canScrollY)) { return } + + // Webkit browsers on OS X abort momentum scrolls when the target + // of the scroll event is removed from the scrollable element. + // This hack (see related code in patchDisplay) makes sure the + // element is kept around. + if (dy && mac && webkit) { + outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) { + for (var i = 0; i < view.length; i++) { + if (view[i].node == cur) { + cm.display.currentWheelTarget = cur; + break outer + } + } + } + } + + // On some browsers, horizontal scrolling will cause redraws to + // happen before the gutter has been realigned, causing it to + // wriggle around in a most unseemly way. When we have an + // estimated pixels/delta value, we just handle horizontal + // scrolling entirely here. It'll be slightly off from native, but + // better than glitching out. + if (dx && !gecko && !presto && wheelPixelsPerUnit != null) { + if (dy && canScrollY) + { updateScrollTop(cm, Math.max(0, scroll.scrollTop + dy * wheelPixelsPerUnit)); } + setScrollLeft(cm, Math.max(0, scroll.scrollLeft + dx * wheelPixelsPerUnit)); + // Only prevent default scrolling if vertical scrolling is + // actually possible. Otherwise, it causes vertical scroll + // jitter on OSX trackpads when deltaX is small and deltaY + // is large (issue #3579) + if (!dy || (dy && canScrollY)) + { e_preventDefault(e); } + display.wheelStartX = null; // Abort measurement, if in progress + return + } + + // 'Project' the visible viewport to cover the area that is being + // scrolled into view (if we know enough to estimate it). + if (dy && wheelPixelsPerUnit != null) { + var pixels = dy * wheelPixelsPerUnit; + var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight; + if (pixels < 0) { top = Math.max(0, top + pixels - 50); } + else { bot = Math.min(cm.doc.height, bot + pixels + 50); } + updateDisplaySimple(cm, {top: top, bottom: bot}); + } + + if (wheelSamples < 20) { + if (display.wheelStartX == null) { + display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop; + display.wheelDX = dx; display.wheelDY = dy; + setTimeout(function () { + if (display.wheelStartX == null) { return } + var movedX = scroll.scrollLeft - display.wheelStartX; + var movedY = scroll.scrollTop - display.wheelStartY; + var sample = (movedY && display.wheelDY && movedY / display.wheelDY) || + (movedX && display.wheelDX && movedX / display.wheelDX); + display.wheelStartX = display.wheelStartY = null; + if (!sample) { return } + wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1); + ++wheelSamples; + }, 200); + } else { + display.wheelDX += dx; display.wheelDY += dy; + } + } + } + + // Selection objects are immutable. A new one is created every time + // the selection changes. A selection is one or more non-overlapping + // (and non-touching) ranges, sorted, and an integer that indicates + // which one is the primary selection (the one that's scrolled into + // view, that getCursor returns, etc). + var Selection = function(ranges, primIndex) { + this.ranges = ranges; + this.primIndex = primIndex; + }; + + Selection.prototype.primary = function () { return this.ranges[this.primIndex] }; + + Selection.prototype.equals = function (other) { + var this$1 = this; + + if (other == this) { return true } + if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) { return false } + for (var i = 0; i < this.ranges.length; i++) { + var here = this$1.ranges[i], there = other.ranges[i]; + if (!equalCursorPos(here.anchor, there.anchor) || !equalCursorPos(here.head, there.head)) { return false } + } + return true + }; + + Selection.prototype.deepCopy = function () { + var this$1 = this; + + var out = []; + for (var i = 0; i < this.ranges.length; i++) + { out[i] = new Range(copyPos(this$1.ranges[i].anchor), copyPos(this$1.ranges[i].head)); } + return new Selection(out, this.primIndex) + }; + + Selection.prototype.somethingSelected = function () { + var this$1 = this; + + for (var i = 0; i < this.ranges.length; i++) + { if (!this$1.ranges[i].empty()) { return true } } + return false + }; + + Selection.prototype.contains = function (pos, end) { + var this$1 = this; + + if (!end) { end = pos; } + for (var i = 0; i < this.ranges.length; i++) { + var range = this$1.ranges[i]; + if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0) + { return i } + } + return -1 + }; + + var Range = function(anchor, head) { + this.anchor = anchor; this.head = head; + }; + + Range.prototype.from = function () { return minPos(this.anchor, this.head) }; + Range.prototype.to = function () { return maxPos(this.anchor, this.head) }; + Range.prototype.empty = function () { return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch }; + + // Take an unsorted, potentially overlapping set of ranges, and + // build a selection out of it. 'Consumes' ranges array (modifying + // it). + function normalizeSelection(cm, ranges, primIndex) { + var mayTouch = cm && cm.options.selectionsMayTouch; + var prim = ranges[primIndex]; + ranges.sort(function (a, b) { return cmp(a.from(), b.from()); }); + primIndex = indexOf(ranges, prim); + for (var i = 1; i < ranges.length; i++) { + var cur = ranges[i], prev = ranges[i - 1]; + var diff = cmp(prev.to(), cur.from()); + if (mayTouch && !cur.empty() ? diff > 0 : diff >= 0) { + var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to()); + var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head; + if (i <= primIndex) { --primIndex; } + ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to)); + } + } + return new Selection(ranges, primIndex) + } + + function simpleSelection(anchor, head) { + return new Selection([new Range(anchor, head || anchor)], 0) + } + + // Compute the position of the end of a change (its 'to' property + // refers to the pre-change end). + function changeEnd(change) { + if (!change.text) { return change.to } + return Pos(change.from.line + change.text.length - 1, + lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0)) + } + + // Adjust a position to refer to the post-change position of the + // same text, or the end of the change if the change covers it. + function adjustForChange(pos, change) { + if (cmp(pos, change.from) < 0) { return pos } + if (cmp(pos, change.to) <= 0) { return changeEnd(change) } + + var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch; + if (pos.line == change.to.line) { ch += changeEnd(change).ch - change.to.ch; } + return Pos(line, ch) + } + + function computeSelAfterChange(doc, change) { + var out = []; + for (var i = 0; i < doc.sel.ranges.length; i++) { + var range = doc.sel.ranges[i]; + out.push(new Range(adjustForChange(range.anchor, change), + adjustForChange(range.head, change))); + } + return normalizeSelection(doc.cm, out, doc.sel.primIndex) + } + + function offsetPos(pos, old, nw) { + if (pos.line == old.line) + { return Pos(nw.line, pos.ch - old.ch + nw.ch) } + else + { return Pos(nw.line + (pos.line - old.line), pos.ch) } + } + + // Used by replaceSelections to allow moving the selection to the + // start or around the replaced test. Hint may be "start" or "around". + function computeReplacedSel(doc, changes, hint) { + var out = []; + var oldPrev = Pos(doc.first, 0), newPrev = oldPrev; + for (var i = 0; i < changes.length; i++) { + var change = changes[i]; + var from = offsetPos(change.from, oldPrev, newPrev); + var to = offsetPos(changeEnd(change), oldPrev, newPrev); + oldPrev = change.to; + newPrev = to; + if (hint == "around") { + var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0; + out[i] = new Range(inv ? to : from, inv ? from : to); + } else { + out[i] = new Range(from, from); + } + } + return new Selection(out, doc.sel.primIndex) + } + + // Used to get the editor into a consistent state again when options change. + + function loadMode(cm) { + cm.doc.mode = getMode(cm.options, cm.doc.modeOption); + resetModeState(cm); + } + + function resetModeState(cm) { + cm.doc.iter(function (line) { + if (line.stateAfter) { line.stateAfter = null; } + if (line.styles) { line.styles = null; } + }); + cm.doc.modeFrontier = cm.doc.highlightFrontier = cm.doc.first; + startWorker(cm, 100); + cm.state.modeGen++; + if (cm.curOp) { regChange(cm); } + } + + // DOCUMENT DATA STRUCTURE + + // By default, updates that start and end at the beginning of a line + // are treated specially, in order to make the association of line + // widgets and marker elements with the text behave more intuitive. + function isWholeLineUpdate(doc, change) { + return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" && + (!doc.cm || doc.cm.options.wholeLineUpdateBefore) + } + + // Perform a change on the document data structure. + function updateDoc(doc, change, markedSpans, estimateHeight$$1) { + function spansFor(n) {return markedSpans ? markedSpans[n] : null} + function update(line, text, spans) { + updateLine(line, text, spans, estimateHeight$$1); + signalLater(line, "change", line, change); + } + function linesFor(start, end) { + var result = []; + for (var i = start; i < end; ++i) + { result.push(new Line(text[i], spansFor(i), estimateHeight$$1)); } + return result + } + + var from = change.from, to = change.to, text = change.text; + var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line); + var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line; + + // Adjust the line structure + if (change.full) { + doc.insert(0, linesFor(0, text.length)); + doc.remove(text.length, doc.size - text.length); + } else if (isWholeLineUpdate(doc, change)) { + // This is a whole-line replace. Treated specially to make + // sure line objects move the way they are supposed to. + var added = linesFor(0, text.length - 1); + update(lastLine, lastLine.text, lastSpans); + if (nlines) { doc.remove(from.line, nlines); } + if (added.length) { doc.insert(from.line, added); } + } else if (firstLine == lastLine) { + if (text.length == 1) { + update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans); + } else { + var added$1 = linesFor(1, text.length - 1); + added$1.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight$$1)); + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); + doc.insert(from.line + 1, added$1); + } + } else if (text.length == 1) { + update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0)); + doc.remove(from.line + 1, nlines); + } else { + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); + update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans); + var added$2 = linesFor(1, text.length - 1); + if (nlines > 1) { doc.remove(from.line + 1, nlines - 1); } + doc.insert(from.line + 1, added$2); + } + + signalLater(doc, "change", doc, change); + } + + // Call f for all linked documents. + function linkedDocs(doc, f, sharedHistOnly) { + function propagate(doc, skip, sharedHist) { + if (doc.linked) { for (var i = 0; i < doc.linked.length; ++i) { + var rel = doc.linked[i]; + if (rel.doc == skip) { continue } + var shared = sharedHist && rel.sharedHist; + if (sharedHistOnly && !shared) { continue } + f(rel.doc, shared); + propagate(rel.doc, doc, shared); + } } + } + propagate(doc, null, true); + } + + // Attach a document to an editor. + function attachDoc(cm, doc) { + if (doc.cm) { throw new Error("This document is already in use.") } + cm.doc = doc; + doc.cm = cm; + estimateLineHeights(cm); + loadMode(cm); + setDirectionClass(cm); + if (!cm.options.lineWrapping) { findMaxLine(cm); } + cm.options.mode = doc.modeOption; + regChange(cm); + } + + function setDirectionClass(cm) { + (cm.doc.direction == "rtl" ? addClass : rmClass)(cm.display.lineDiv, "CodeMirror-rtl"); + } + + function directionChanged(cm) { + runInOp(cm, function () { + setDirectionClass(cm); + regChange(cm); + }); + } + + function History(startGen) { + // Arrays of change events and selections. Doing something adds an + // event to done and clears undo. Undoing moves events from done + // to undone, redoing moves them in the other direction. + this.done = []; this.undone = []; + this.undoDepth = Infinity; + // Used to track when changes can be merged into a single undo + // event + this.lastModTime = this.lastSelTime = 0; + this.lastOp = this.lastSelOp = null; + this.lastOrigin = this.lastSelOrigin = null; + // Used by the isClean() method + this.generation = this.maxGeneration = startGen || 1; + } + + // Create a history change event from an updateDoc-style change + // object. + function historyChangeFromChange(doc, change) { + var histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)}; + attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); + linkedDocs(doc, function (doc) { return attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); }, true); + return histChange + } + + // Pop all selection events off the end of a history array. Stop at + // a change event. + function clearSelectionEvents(array) { + while (array.length) { + var last = lst(array); + if (last.ranges) { array.pop(); } + else { break } + } + } + + // Find the top change event in the history. Pop off selection + // events that are in the way. + function lastChangeEvent(hist, force) { + if (force) { + clearSelectionEvents(hist.done); + return lst(hist.done) + } else if (hist.done.length && !lst(hist.done).ranges) { + return lst(hist.done) + } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) { + hist.done.pop(); + return lst(hist.done) + } + } + + // Register a change in the history. Merges changes that are within + // a single operation, or are close together with an origin that + // allows merging (starting with "+") into a single event. + function addChangeToHistory(doc, change, selAfter, opId) { + var hist = doc.history; + hist.undone.length = 0; + var time = +new Date, cur; + var last; + + if ((hist.lastOp == opId || + hist.lastOrigin == change.origin && change.origin && + ((change.origin.charAt(0) == "+" && hist.lastModTime > time - (doc.cm ? doc.cm.options.historyEventDelay : 500)) || + change.origin.charAt(0) == "*")) && + (cur = lastChangeEvent(hist, hist.lastOp == opId))) { + // Merge this change into the last event + last = lst(cur.changes); + if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) { + // Optimized case for simple insertion -- don't want to add + // new changesets for every character typed + last.to = changeEnd(change); + } else { + // Add new sub-event + cur.changes.push(historyChangeFromChange(doc, change)); + } + } else { + // Can not be merged, start a new event. + var before = lst(hist.done); + if (!before || !before.ranges) + { pushSelectionToHistory(doc.sel, hist.done); } + cur = {changes: [historyChangeFromChange(doc, change)], + generation: hist.generation}; + hist.done.push(cur); + while (hist.done.length > hist.undoDepth) { + hist.done.shift(); + if (!hist.done[0].ranges) { hist.done.shift(); } + } + } + hist.done.push(selAfter); + hist.generation = ++hist.maxGeneration; + hist.lastModTime = hist.lastSelTime = time; + hist.lastOp = hist.lastSelOp = opId; + hist.lastOrigin = hist.lastSelOrigin = change.origin; + + if (!last) { signal(doc, "historyAdded"); } + } + + function selectionEventCanBeMerged(doc, origin, prev, sel) { + var ch = origin.charAt(0); + return ch == "*" || + ch == "+" && + prev.ranges.length == sel.ranges.length && + prev.somethingSelected() == sel.somethingSelected() && + new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500) + } + + // Called whenever the selection changes, sets the new selection as + // the pending selection in the history, and pushes the old pending + // selection into the 'done' array when it was significantly + // different (in number of selected ranges, emptiness, or time). + function addSelectionToHistory(doc, sel, opId, options) { + var hist = doc.history, origin = options && options.origin; + + // A new event is started when the previous origin does not match + // the current, or the origins don't allow matching. Origins + // starting with * are always merged, those starting with + are + // merged when similar and close together in time. + if (opId == hist.lastSelOp || + (origin && hist.lastSelOrigin == origin && + (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin || + selectionEventCanBeMerged(doc, origin, lst(hist.done), sel)))) + { hist.done[hist.done.length - 1] = sel; } + else + { pushSelectionToHistory(sel, hist.done); } + + hist.lastSelTime = +new Date; + hist.lastSelOrigin = origin; + hist.lastSelOp = opId; + if (options && options.clearRedo !== false) + { clearSelectionEvents(hist.undone); } + } + + function pushSelectionToHistory(sel, dest) { + var top = lst(dest); + if (!(top && top.ranges && top.equals(sel))) + { dest.push(sel); } + } + + // Used to store marked span information in the history. + function attachLocalSpans(doc, change, from, to) { + var existing = change["spans_" + doc.id], n = 0; + doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function (line) { + if (line.markedSpans) + { (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans; } + ++n; + }); + } + + // When un/re-doing restores text containing marked spans, those + // that have been explicitly cleared should not be restored. + function removeClearedSpans(spans) { + if (!spans) { return null } + var out; + for (var i = 0; i < spans.length; ++i) { + if (spans[i].marker.explicitlyCleared) { if (!out) { out = spans.slice(0, i); } } + else if (out) { out.push(spans[i]); } + } + return !out ? spans : out.length ? out : null + } + + // Retrieve and filter the old marked spans stored in a change event. + function getOldSpans(doc, change) { + var found = change["spans_" + doc.id]; + if (!found) { return null } + var nw = []; + for (var i = 0; i < change.text.length; ++i) + { nw.push(removeClearedSpans(found[i])); } + return nw + } + + // Used for un/re-doing changes from the history. Combines the + // result of computing the existing spans with the set of spans that + // existed in the history (so that deleting around a span and then + // undoing brings back the span). + function mergeOldSpans(doc, change) { + var old = getOldSpans(doc, change); + var stretched = stretchSpansOverChange(doc, change); + if (!old) { return stretched } + if (!stretched) { return old } + + for (var i = 0; i < old.length; ++i) { + var oldCur = old[i], stretchCur = stretched[i]; + if (oldCur && stretchCur) { + spans: for (var j = 0; j < stretchCur.length; ++j) { + var span = stretchCur[j]; + for (var k = 0; k < oldCur.length; ++k) + { if (oldCur[k].marker == span.marker) { continue spans } } + oldCur.push(span); + } + } else if (stretchCur) { + old[i] = stretchCur; + } + } + return old + } + + // Used both to provide a JSON-safe object in .getHistory, and, when + // detaching a document, to split the history in two + function copyHistoryArray(events, newGroup, instantiateSel) { + var copy = []; + for (var i = 0; i < events.length; ++i) { + var event = events[i]; + if (event.ranges) { + copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event); + continue + } + var changes = event.changes, newChanges = []; + copy.push({changes: newChanges}); + for (var j = 0; j < changes.length; ++j) { + var change = changes[j], m = (void 0); + newChanges.push({from: change.from, to: change.to, text: change.text}); + if (newGroup) { for (var prop in change) { if (m = prop.match(/^spans_(\d+)$/)) { + if (indexOf(newGroup, Number(m[1])) > -1) { + lst(newChanges)[prop] = change[prop]; + delete change[prop]; + } + } } } + } + } + return copy + } + + // The 'scroll' parameter given to many of these indicated whether + // the new cursor position should be scrolled into view after + // modifying the selection. + + // If shift is held or the extend flag is set, extends a range to + // include a given position (and optionally a second position). + // Otherwise, simply returns the range between the given positions. + // Used for cursor motion and such. + function extendRange(range, head, other, extend) { + if (extend) { + var anchor = range.anchor; + if (other) { + var posBefore = cmp(head, anchor) < 0; + if (posBefore != (cmp(other, anchor) < 0)) { + anchor = head; + head = other; + } else if (posBefore != (cmp(head, other) < 0)) { + head = other; + } + } + return new Range(anchor, head) + } else { + return new Range(other || head, head) + } + } + + // Extend the primary selection range, discard the rest. + function extendSelection(doc, head, other, options, extend) { + if (extend == null) { extend = doc.cm && (doc.cm.display.shift || doc.extend); } + setSelection(doc, new Selection([extendRange(doc.sel.primary(), head, other, extend)], 0), options); + } + + // Extend all selections (pos is an array of selections with length + // equal the number of selections) + function extendSelections(doc, heads, options) { + var out = []; + var extend = doc.cm && (doc.cm.display.shift || doc.extend); + for (var i = 0; i < doc.sel.ranges.length; i++) + { out[i] = extendRange(doc.sel.ranges[i], heads[i], null, extend); } + var newSel = normalizeSelection(doc.cm, out, doc.sel.primIndex); + setSelection(doc, newSel, options); + } + + // Updates a single range in the selection. + function replaceOneSelection(doc, i, range, options) { + var ranges = doc.sel.ranges.slice(0); + ranges[i] = range; + setSelection(doc, normalizeSelection(doc.cm, ranges, doc.sel.primIndex), options); + } + + // Reset the selection to a single range. + function setSimpleSelection(doc, anchor, head, options) { + setSelection(doc, simpleSelection(anchor, head), options); + } + + // Give beforeSelectionChange handlers a change to influence a + // selection update. + function filterSelectionChange(doc, sel, options) { + var obj = { + ranges: sel.ranges, + update: function(ranges) { + var this$1 = this; + + this.ranges = []; + for (var i = 0; i < ranges.length; i++) + { this$1.ranges[i] = new Range(clipPos(doc, ranges[i].anchor), + clipPos(doc, ranges[i].head)); } + }, + origin: options && options.origin + }; + signal(doc, "beforeSelectionChange", doc, obj); + if (doc.cm) { signal(doc.cm, "beforeSelectionChange", doc.cm, obj); } + if (obj.ranges != sel.ranges) { return normalizeSelection(doc.cm, obj.ranges, obj.ranges.length - 1) } + else { return sel } + } + + function setSelectionReplaceHistory(doc, sel, options) { + var done = doc.history.done, last = lst(done); + if (last && last.ranges) { + done[done.length - 1] = sel; + setSelectionNoUndo(doc, sel, options); + } else { + setSelection(doc, sel, options); + } + } + + // Set a new selection. + function setSelection(doc, sel, options) { + setSelectionNoUndo(doc, sel, options); + addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options); + } + + function setSelectionNoUndo(doc, sel, options) { + if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) + { sel = filterSelectionChange(doc, sel, options); } + + var bias = options && options.bias || + (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1); + setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true)); + + if (!(options && options.scroll === false) && doc.cm) + { ensureCursorVisible(doc.cm); } + } + + function setSelectionInner(doc, sel) { + if (sel.equals(doc.sel)) { return } + + doc.sel = sel; + + if (doc.cm) { + doc.cm.curOp.updateInput = 1; + doc.cm.curOp.selectionChanged = true; + signalCursorActivity(doc.cm); + } + signalLater(doc, "cursorActivity", doc); + } + + // Verify that the selection does not partially select any atomic + // marked ranges. + function reCheckSelection(doc) { + setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false)); + } + + // Return a selection that does not partially select any atomic + // ranges. + function skipAtomicInSelection(doc, sel, bias, mayClear) { + var out; + for (var i = 0; i < sel.ranges.length; i++) { + var range = sel.ranges[i]; + var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i]; + var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear); + var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear); + if (out || newAnchor != range.anchor || newHead != range.head) { + if (!out) { out = sel.ranges.slice(0, i); } + out[i] = new Range(newAnchor, newHead); + } + } + return out ? normalizeSelection(doc.cm, out, sel.primIndex) : sel + } + + function skipAtomicInner(doc, pos, oldPos, dir, mayClear) { + var line = getLine(doc, pos.line); + if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) { + var sp = line.markedSpans[i], m = sp.marker; + if ((sp.from == null || (m.inclusiveLeft ? sp.from <= pos.ch : sp.from < pos.ch)) && + (sp.to == null || (m.inclusiveRight ? sp.to >= pos.ch : sp.to > pos.ch))) { + if (mayClear) { + signal(m, "beforeCursorEnter"); + if (m.explicitlyCleared) { + if (!line.markedSpans) { break } + else {--i; continue} + } + } + if (!m.atomic) { continue } + + if (oldPos) { + var near = m.find(dir < 0 ? 1 : -1), diff = (void 0); + if (dir < 0 ? m.inclusiveRight : m.inclusiveLeft) + { near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null); } + if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0)) + { return skipAtomicInner(doc, near, pos, dir, mayClear) } + } + + var far = m.find(dir < 0 ? -1 : 1); + if (dir < 0 ? m.inclusiveLeft : m.inclusiveRight) + { far = movePos(doc, far, dir, far.line == pos.line ? line : null); } + return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null + } + } } + return pos + } + + // Ensure a given position is not inside an atomic range. + function skipAtomic(doc, pos, oldPos, bias, mayClear) { + var dir = bias || 1; + var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) || + (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) || + skipAtomicInner(doc, pos, oldPos, -dir, mayClear) || + (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true)); + if (!found) { + doc.cantEdit = true; + return Pos(doc.first, 0) + } + return found + } + + function movePos(doc, pos, dir, line) { + if (dir < 0 && pos.ch == 0) { + if (pos.line > doc.first) { return clipPos(doc, Pos(pos.line - 1)) } + else { return null } + } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) { + if (pos.line < doc.first + doc.size - 1) { return Pos(pos.line + 1, 0) } + else { return null } + } else { + return new Pos(pos.line, pos.ch + dir) + } + } + + function selectAll(cm) { + cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll); + } + + // UPDATING + + // Allow "beforeChange" event handlers to influence a change + function filterChange(doc, change, update) { + var obj = { + canceled: false, + from: change.from, + to: change.to, + text: change.text, + origin: change.origin, + cancel: function () { return obj.canceled = true; } + }; + if (update) { obj.update = function (from, to, text, origin) { + if (from) { obj.from = clipPos(doc, from); } + if (to) { obj.to = clipPos(doc, to); } + if (text) { obj.text = text; } + if (origin !== undefined) { obj.origin = origin; } + }; } + signal(doc, "beforeChange", doc, obj); + if (doc.cm) { signal(doc.cm, "beforeChange", doc.cm, obj); } + + if (obj.canceled) { + if (doc.cm) { doc.cm.curOp.updateInput = 2; } + return null + } + return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin} + } + + // Apply a change to a document, and add it to the document's + // history, and propagating it to all linked documents. + function makeChange(doc, change, ignoreReadOnly) { + if (doc.cm) { + if (!doc.cm.curOp) { return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly) } + if (doc.cm.state.suppressEdits) { return } + } + + if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) { + change = filterChange(doc, change, true); + if (!change) { return } + } + + // Possibly split or suppress the update based on the presence + // of read-only spans in its range. + var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to); + if (split) { + for (var i = split.length - 1; i >= 0; --i) + { makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text, origin: change.origin}); } + } else { + makeChangeInner(doc, change); + } + } + + function makeChangeInner(doc, change) { + if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) { return } + var selAfter = computeSelAfterChange(doc, change); + addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN); + + makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change)); + var rebased = []; + + linkedDocs(doc, function (doc, sharedHist) { + if (!sharedHist && indexOf(rebased, doc.history) == -1) { + rebaseHist(doc.history, change); + rebased.push(doc.history); + } + makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)); + }); + } + + // Revert a change stored in a document's history. + function makeChangeFromHistory(doc, type, allowSelectionOnly) { + var suppress = doc.cm && doc.cm.state.suppressEdits; + if (suppress && !allowSelectionOnly) { return } + + var hist = doc.history, event, selAfter = doc.sel; + var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done; + + // Verify that there is a useable event (so that ctrl-z won't + // needlessly clear selection events) + var i = 0; + for (; i < source.length; i++) { + event = source[i]; + if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges) + { break } + } + if (i == source.length) { return } + hist.lastOrigin = hist.lastSelOrigin = null; + + for (;;) { + event = source.pop(); + if (event.ranges) { + pushSelectionToHistory(event, dest); + if (allowSelectionOnly && !event.equals(doc.sel)) { + setSelection(doc, event, {clearRedo: false}); + return + } + selAfter = event; + } else if (suppress) { + source.push(event); + return + } else { break } + } + + // Build up a reverse change object to add to the opposite history + // stack (redo when undoing, and vice versa). + var antiChanges = []; + pushSelectionToHistory(selAfter, dest); + dest.push({changes: antiChanges, generation: hist.generation}); + hist.generation = event.generation || ++hist.maxGeneration; + + var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange"); + + var loop = function ( i ) { + var change = event.changes[i]; + change.origin = type; + if (filter && !filterChange(doc, change, false)) { + source.length = 0; + return {} + } + + antiChanges.push(historyChangeFromChange(doc, change)); + + var after = i ? computeSelAfterChange(doc, change) : lst(source); + makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)); + if (!i && doc.cm) { doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)}); } + var rebased = []; + + // Propagate to the linked documents + linkedDocs(doc, function (doc, sharedHist) { + if (!sharedHist && indexOf(rebased, doc.history) == -1) { + rebaseHist(doc.history, change); + rebased.push(doc.history); + } + makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change)); + }); + }; + + for (var i$1 = event.changes.length - 1; i$1 >= 0; --i$1) { + var returned = loop( i$1 ); + + if ( returned ) return returned.v; + } + } + + // Sub-views need their line numbers shifted when text is added + // above or below them in the parent document. + function shiftDoc(doc, distance) { + if (distance == 0) { return } + doc.first += distance; + doc.sel = new Selection(map(doc.sel.ranges, function (range) { return new Range( + Pos(range.anchor.line + distance, range.anchor.ch), + Pos(range.head.line + distance, range.head.ch) + ); }), doc.sel.primIndex); + if (doc.cm) { + regChange(doc.cm, doc.first, doc.first - distance, distance); + for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++) + { regLineChange(doc.cm, l, "gutter"); } + } + } + + // More lower-level change function, handling only a single document + // (not linked ones). + function makeChangeSingleDoc(doc, change, selAfter, spans) { + if (doc.cm && !doc.cm.curOp) + { return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans) } + + if (change.to.line < doc.first) { + shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line)); + return + } + if (change.from.line > doc.lastLine()) { return } + + // Clip the change to the size of this doc + if (change.from.line < doc.first) { + var shift = change.text.length - 1 - (doc.first - change.from.line); + shiftDoc(doc, shift); + change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch), + text: [lst(change.text)], origin: change.origin}; + } + var last = doc.lastLine(); + if (change.to.line > last) { + change = {from: change.from, to: Pos(last, getLine(doc, last).text.length), + text: [change.text[0]], origin: change.origin}; + } + + change.removed = getBetween(doc, change.from, change.to); + + if (!selAfter) { selAfter = computeSelAfterChange(doc, change); } + if (doc.cm) { makeChangeSingleDocInEditor(doc.cm, change, spans); } + else { updateDoc(doc, change, spans); } + setSelectionNoUndo(doc, selAfter, sel_dontScroll); + } + + // Handle the interaction of a change to a document with the editor + // that this document is part of. + function makeChangeSingleDocInEditor(cm, change, spans) { + var doc = cm.doc, display = cm.display, from = change.from, to = change.to; + + var recomputeMaxLength = false, checkWidthStart = from.line; + if (!cm.options.lineWrapping) { + checkWidthStart = lineNo(visualLine(getLine(doc, from.line))); + doc.iter(checkWidthStart, to.line + 1, function (line) { + if (line == display.maxLine) { + recomputeMaxLength = true; + return true + } + }); + } + + if (doc.sel.contains(change.from, change.to) > -1) + { signalCursorActivity(cm); } + + updateDoc(doc, change, spans, estimateHeight(cm)); + + if (!cm.options.lineWrapping) { + doc.iter(checkWidthStart, from.line + change.text.length, function (line) { + var len = lineLength(line); + if (len > display.maxLineLength) { + display.maxLine = line; + display.maxLineLength = len; + display.maxLineChanged = true; + recomputeMaxLength = false; + } + }); + if (recomputeMaxLength) { cm.curOp.updateMaxLine = true; } + } + + retreatFrontier(doc, from.line); + startWorker(cm, 400); + + var lendiff = change.text.length - (to.line - from.line) - 1; + // Remember that these lines changed, for updating the display + if (change.full) + { regChange(cm); } + else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change)) + { regLineChange(cm, from.line, "text"); } + else + { regChange(cm, from.line, to.line + 1, lendiff); } + + var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change"); + if (changeHandler || changesHandler) { + var obj = { + from: from, to: to, + text: change.text, + removed: change.removed, + origin: change.origin + }; + if (changeHandler) { signalLater(cm, "change", cm, obj); } + if (changesHandler) { (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj); } + } + cm.display.selForContextMenu = null; + } + + function replaceRange(doc, code, from, to, origin) { + var assign; + + if (!to) { to = from; } + if (cmp(to, from) < 0) { (assign = [to, from], from = assign[0], to = assign[1]); } + if (typeof code == "string") { code = doc.splitLines(code); } + makeChange(doc, {from: from, to: to, text: code, origin: origin}); + } + + // Rebasing/resetting history to deal with externally-sourced changes + + function rebaseHistSelSingle(pos, from, to, diff) { + if (to < pos.line) { + pos.line += diff; + } else if (from < pos.line) { + pos.line = from; + pos.ch = 0; + } + } + + // Tries to rebase an array of history events given a change in the + // document. If the change touches the same lines as the event, the + // event, and everything 'behind' it, is discarded. If the change is + // before the event, the event's positions are updated. Uses a + // copy-on-write scheme for the positions, to avoid having to + // reallocate them all on every rebase, but also avoid problems with + // shared position objects being unsafely updated. + function rebaseHistArray(array, from, to, diff) { + for (var i = 0; i < array.length; ++i) { + var sub = array[i], ok = true; + if (sub.ranges) { + if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true; } + for (var j = 0; j < sub.ranges.length; j++) { + rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff); + rebaseHistSelSingle(sub.ranges[j].head, from, to, diff); + } + continue + } + for (var j$1 = 0; j$1 < sub.changes.length; ++j$1) { + var cur = sub.changes[j$1]; + if (to < cur.from.line) { + cur.from = Pos(cur.from.line + diff, cur.from.ch); + cur.to = Pos(cur.to.line + diff, cur.to.ch); + } else if (from <= cur.to.line) { + ok = false; + break + } + } + if (!ok) { + array.splice(0, i + 1); + i = 0; + } + } + } + + function rebaseHist(hist, change) { + var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1; + rebaseHistArray(hist.done, from, to, diff); + rebaseHistArray(hist.undone, from, to, diff); + } + + // Utility for applying a change to a line by handle or number, + // returning the number and optionally registering the line as + // changed. + function changeLine(doc, handle, changeType, op) { + var no = handle, line = handle; + if (typeof handle == "number") { line = getLine(doc, clipLine(doc, handle)); } + else { no = lineNo(handle); } + if (no == null) { return null } + if (op(line, no) && doc.cm) { regLineChange(doc.cm, no, changeType); } + return line + } + + // The document is represented as a BTree consisting of leaves, with + // chunk of lines in them, and branches, with up to ten leaves or + // other branch nodes below them. The top node is always a branch + // node, and is the document object itself (meaning it has + // additional methods and properties). + // + // All nodes have parent links. The tree is used both to go from + // line numbers to line objects, and to go from objects to numbers. + // It also indexes by height, and is used to convert between height + // and line object, and to find the total height of the document. + // + // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html + + function LeafChunk(lines) { + var this$1 = this; + + this.lines = lines; + this.parent = null; + var height = 0; + for (var i = 0; i < lines.length; ++i) { + lines[i].parent = this$1; + height += lines[i].height; + } + this.height = height; + } + + LeafChunk.prototype = { + chunkSize: function() { return this.lines.length }, + + // Remove the n lines at offset 'at'. + removeInner: function(at, n) { + var this$1 = this; + + for (var i = at, e = at + n; i < e; ++i) { + var line = this$1.lines[i]; + this$1.height -= line.height; + cleanUpLine(line); + signalLater(line, "delete"); + } + this.lines.splice(at, n); + }, + + // Helper used to collapse a small branch into a single leaf. + collapse: function(lines) { + lines.push.apply(lines, this.lines); + }, + + // Insert the given array of lines at offset 'at', count them as + // having the given height. + insertInner: function(at, lines, height) { + var this$1 = this; + + this.height += height; + this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)); + for (var i = 0; i < lines.length; ++i) { lines[i].parent = this$1; } + }, + + // Used to iterate over a part of the tree. + iterN: function(at, n, op) { + var this$1 = this; + + for (var e = at + n; at < e; ++at) + { if (op(this$1.lines[at])) { return true } } + } + }; + + function BranchChunk(children) { + var this$1 = this; + + this.children = children; + var size = 0, height = 0; + for (var i = 0; i < children.length; ++i) { + var ch = children[i]; + size += ch.chunkSize(); height += ch.height; + ch.parent = this$1; + } + this.size = size; + this.height = height; + this.parent = null; + } + + BranchChunk.prototype = { + chunkSize: function() { return this.size }, + + removeInner: function(at, n) { + var this$1 = this; + + this.size -= n; + for (var i = 0; i < this.children.length; ++i) { + var child = this$1.children[i], sz = child.chunkSize(); + if (at < sz) { + var rm = Math.min(n, sz - at), oldHeight = child.height; + child.removeInner(at, rm); + this$1.height -= oldHeight - child.height; + if (sz == rm) { this$1.children.splice(i--, 1); child.parent = null; } + if ((n -= rm) == 0) { break } + at = 0; + } else { at -= sz; } + } + // If the result is smaller than 25 lines, ensure that it is a + // single leaf node. + if (this.size - n < 25 && + (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) { + var lines = []; + this.collapse(lines); + this.children = [new LeafChunk(lines)]; + this.children[0].parent = this; + } + }, + + collapse: function(lines) { + var this$1 = this; + + for (var i = 0; i < this.children.length; ++i) { this$1.children[i].collapse(lines); } + }, + + insertInner: function(at, lines, height) { + var this$1 = this; + + this.size += lines.length; + this.height += height; + for (var i = 0; i < this.children.length; ++i) { + var child = this$1.children[i], sz = child.chunkSize(); + if (at <= sz) { + child.insertInner(at, lines, height); + if (child.lines && child.lines.length > 50) { + // To avoid memory thrashing when child.lines is huge (e.g. first view of a large file), it's never spliced. + // Instead, small slices are taken. They're taken in order because sequential memory accesses are fastest. + var remaining = child.lines.length % 25 + 25; + for (var pos = remaining; pos < child.lines.length;) { + var leaf = new LeafChunk(child.lines.slice(pos, pos += 25)); + child.height -= leaf.height; + this$1.children.splice(++i, 0, leaf); + leaf.parent = this$1; + } + child.lines = child.lines.slice(0, remaining); + this$1.maybeSpill(); + } + break + } + at -= sz; + } + }, + + // When a node has grown, check whether it should be split. + maybeSpill: function() { + if (this.children.length <= 10) { return } + var me = this; + do { + var spilled = me.children.splice(me.children.length - 5, 5); + var sibling = new BranchChunk(spilled); + if (!me.parent) { // Become the parent node + var copy = new BranchChunk(me.children); + copy.parent = me; + me.children = [copy, sibling]; + me = copy; + } else { + me.size -= sibling.size; + me.height -= sibling.height; + var myIndex = indexOf(me.parent.children, me); + me.parent.children.splice(myIndex + 1, 0, sibling); + } + sibling.parent = me.parent; + } while (me.children.length > 10) + me.parent.maybeSpill(); + }, + + iterN: function(at, n, op) { + var this$1 = this; + + for (var i = 0; i < this.children.length; ++i) { + var child = this$1.children[i], sz = child.chunkSize(); + if (at < sz) { + var used = Math.min(n, sz - at); + if (child.iterN(at, used, op)) { return true } + if ((n -= used) == 0) { break } + at = 0; + } else { at -= sz; } + } + } + }; + + // Line widgets are block elements displayed above or below a line. + + var LineWidget = function(doc, node, options) { + var this$1 = this; + + if (options) { for (var opt in options) { if (options.hasOwnProperty(opt)) + { this$1[opt] = options[opt]; } } } + this.doc = doc; + this.node = node; + }; + + LineWidget.prototype.clear = function () { + var this$1 = this; + + var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line); + if (no == null || !ws) { return } + for (var i = 0; i < ws.length; ++i) { if (ws[i] == this$1) { ws.splice(i--, 1); } } + if (!ws.length) { line.widgets = null; } + var height = widgetHeight(this); + updateLineHeight(line, Math.max(0, line.height - height)); + if (cm) { + runInOp(cm, function () { + adjustScrollWhenAboveVisible(cm, line, -height); + regLineChange(cm, no, "widget"); + }); + signalLater(cm, "lineWidgetCleared", cm, this, no); + } + }; + + LineWidget.prototype.changed = function () { + var this$1 = this; + + var oldH = this.height, cm = this.doc.cm, line = this.line; + this.height = null; + var diff = widgetHeight(this) - oldH; + if (!diff) { return } + if (!lineIsHidden(this.doc, line)) { updateLineHeight(line, line.height + diff); } + if (cm) { + runInOp(cm, function () { + cm.curOp.forceUpdate = true; + adjustScrollWhenAboveVisible(cm, line, diff); + signalLater(cm, "lineWidgetChanged", cm, this$1, lineNo(line)); + }); + } + }; + eventMixin(LineWidget); + + function adjustScrollWhenAboveVisible(cm, line, diff) { + if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop)) + { addToScrollTop(cm, diff); } + } + + function addLineWidget(doc, handle, node, options) { + var widget = new LineWidget(doc, node, options); + var cm = doc.cm; + if (cm && widget.noHScroll) { cm.display.alignWidgets = true; } + changeLine(doc, handle, "widget", function (line) { + var widgets = line.widgets || (line.widgets = []); + if (widget.insertAt == null) { widgets.push(widget); } + else { widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget); } + widget.line = line; + if (cm && !lineIsHidden(doc, line)) { + var aboveVisible = heightAtLine(line) < doc.scrollTop; + updateLineHeight(line, line.height + widgetHeight(widget)); + if (aboveVisible) { addToScrollTop(cm, widget.height); } + cm.curOp.forceUpdate = true; + } + return true + }); + if (cm) { signalLater(cm, "lineWidgetAdded", cm, widget, typeof handle == "number" ? handle : lineNo(handle)); } + return widget + } + + // TEXTMARKERS + + // Created with markText and setBookmark methods. A TextMarker is a + // handle that can be used to clear or find a marked position in the + // document. Line objects hold arrays (markedSpans) containing + // {from, to, marker} object pointing to such marker objects, and + // indicating that such a marker is present on that line. Multiple + // lines may point to the same marker when it spans across lines. + // The spans will have null for their from/to properties when the + // marker continues beyond the start/end of the line. Markers have + // links back to the lines they currently touch. + + // Collapsed markers have unique ids, in order to be able to order + // them, which is needed for uniquely determining an outer marker + // when they overlap (they may nest, but not partially overlap). + var nextMarkerId = 0; + + var TextMarker = function(doc, type) { + this.lines = []; + this.type = type; + this.doc = doc; + this.id = ++nextMarkerId; + }; + + // Clear the marker. + TextMarker.prototype.clear = function () { + var this$1 = this; + + if (this.explicitlyCleared) { return } + var cm = this.doc.cm, withOp = cm && !cm.curOp; + if (withOp) { startOperation(cm); } + if (hasHandler(this, "clear")) { + var found = this.find(); + if (found) { signalLater(this, "clear", found.from, found.to); } + } + var min = null, max = null; + for (var i = 0; i < this.lines.length; ++i) { + var line = this$1.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this$1); + if (cm && !this$1.collapsed) { regLineChange(cm, lineNo(line), "text"); } + else if (cm) { + if (span.to != null) { max = lineNo(line); } + if (span.from != null) { min = lineNo(line); } + } + line.markedSpans = removeMarkedSpan(line.markedSpans, span); + if (span.from == null && this$1.collapsed && !lineIsHidden(this$1.doc, line) && cm) + { updateLineHeight(line, textHeight(cm.display)); } + } + if (cm && this.collapsed && !cm.options.lineWrapping) { for (var i$1 = 0; i$1 < this.lines.length; ++i$1) { + var visual = visualLine(this$1.lines[i$1]), len = lineLength(visual); + if (len > cm.display.maxLineLength) { + cm.display.maxLine = visual; + cm.display.maxLineLength = len; + cm.display.maxLineChanged = true; + } + } } + + if (min != null && cm && this.collapsed) { regChange(cm, min, max + 1); } + this.lines.length = 0; + this.explicitlyCleared = true; + if (this.atomic && this.doc.cantEdit) { + this.doc.cantEdit = false; + if (cm) { reCheckSelection(cm.doc); } + } + if (cm) { signalLater(cm, "markerCleared", cm, this, min, max); } + if (withOp) { endOperation(cm); } + if (this.parent) { this.parent.clear(); } + }; + + // Find the position of the marker in the document. Returns a {from, + // to} object by default. Side can be passed to get a specific side + // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the + // Pos objects returned contain a line object, rather than a line + // number (used to prevent looking up the same line twice). + TextMarker.prototype.find = function (side, lineObj) { + var this$1 = this; + + if (side == null && this.type == "bookmark") { side = 1; } + var from, to; + for (var i = 0; i < this.lines.length; ++i) { + var line = this$1.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this$1); + if (span.from != null) { + from = Pos(lineObj ? line : lineNo(line), span.from); + if (side == -1) { return from } + } + if (span.to != null) { + to = Pos(lineObj ? line : lineNo(line), span.to); + if (side == 1) { return to } + } + } + return from && {from: from, to: to} + }; + + // Signals that the marker's widget changed, and surrounding layout + // should be recomputed. + TextMarker.prototype.changed = function () { + var this$1 = this; + + var pos = this.find(-1, true), widget = this, cm = this.doc.cm; + if (!pos || !cm) { return } + runInOp(cm, function () { + var line = pos.line, lineN = lineNo(pos.line); + var view = findViewForLine(cm, lineN); + if (view) { + clearLineMeasurementCacheFor(view); + cm.curOp.selectionChanged = cm.curOp.forceUpdate = true; + } + cm.curOp.updateMaxLine = true; + if (!lineIsHidden(widget.doc, line) && widget.height != null) { + var oldHeight = widget.height; + widget.height = null; + var dHeight = widgetHeight(widget) - oldHeight; + if (dHeight) + { updateLineHeight(line, line.height + dHeight); } + } + signalLater(cm, "markerChanged", cm, this$1); + }); + }; + + TextMarker.prototype.attachLine = function (line) { + if (!this.lines.length && this.doc.cm) { + var op = this.doc.cm.curOp; + if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) + { (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this); } + } + this.lines.push(line); + }; + + TextMarker.prototype.detachLine = function (line) { + this.lines.splice(indexOf(this.lines, line), 1); + if (!this.lines.length && this.doc.cm) { + var op = this.doc.cm.curOp + ;(op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this); + } + }; + eventMixin(TextMarker); + + // Create a marker, wire it up to the right lines, and + function markText(doc, from, to, options, type) { + // Shared markers (across linked documents) are handled separately + // (markTextShared will call out to this again, once per + // document). + if (options && options.shared) { return markTextShared(doc, from, to, options, type) } + // Ensure we are in an operation. + if (doc.cm && !doc.cm.curOp) { return operation(doc.cm, markText)(doc, from, to, options, type) } + + var marker = new TextMarker(doc, type), diff = cmp(from, to); + if (options) { copyObj(options, marker, false); } + // Don't connect empty markers unless clearWhenEmpty is false + if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false) + { return marker } + if (marker.replacedWith) { + // Showing up as a widget implies collapsed (widget replaces text) + marker.collapsed = true; + marker.widgetNode = eltP("span", [marker.replacedWith], "CodeMirror-widget"); + if (!options.handleMouseEvents) { marker.widgetNode.setAttribute("cm-ignore-events", "true"); } + if (options.insertLeft) { marker.widgetNode.insertLeft = true; } + } + if (marker.collapsed) { + if (conflictingCollapsedRange(doc, from.line, from, to, marker) || + from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker)) + { throw new Error("Inserting collapsed marker partially overlapping an existing one") } + seeCollapsedSpans(); + } + + if (marker.addToHistory) + { addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN); } + + var curLine = from.line, cm = doc.cm, updateMaxLine; + doc.iter(curLine, to.line + 1, function (line) { + if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine) + { updateMaxLine = true; } + if (marker.collapsed && curLine != from.line) { updateLineHeight(line, 0); } + addMarkedSpan(line, new MarkedSpan(marker, + curLine == from.line ? from.ch : null, + curLine == to.line ? to.ch : null)); + ++curLine; + }); + // lineIsHidden depends on the presence of the spans, so needs a second pass + if (marker.collapsed) { doc.iter(from.line, to.line + 1, function (line) { + if (lineIsHidden(doc, line)) { updateLineHeight(line, 0); } + }); } + + if (marker.clearOnEnter) { on(marker, "beforeCursorEnter", function () { return marker.clear(); }); } + + if (marker.readOnly) { + seeReadOnlySpans(); + if (doc.history.done.length || doc.history.undone.length) + { doc.clearHistory(); } + } + if (marker.collapsed) { + marker.id = ++nextMarkerId; + marker.atomic = true; + } + if (cm) { + // Sync editor state + if (updateMaxLine) { cm.curOp.updateMaxLine = true; } + if (marker.collapsed) + { regChange(cm, from.line, to.line + 1); } + else if (marker.className || marker.startStyle || marker.endStyle || marker.css || + marker.attributes || marker.title) + { for (var i = from.line; i <= to.line; i++) { regLineChange(cm, i, "text"); } } + if (marker.atomic) { reCheckSelection(cm.doc); } + signalLater(cm, "markerAdded", cm, marker); + } + return marker + } + + // SHARED TEXTMARKERS + + // A shared marker spans multiple linked documents. It is + // implemented as a meta-marker-object controlling multiple normal + // markers. + var SharedTextMarker = function(markers, primary) { + var this$1 = this; + + this.markers = markers; + this.primary = primary; + for (var i = 0; i < markers.length; ++i) + { markers[i].parent = this$1; } + }; + + SharedTextMarker.prototype.clear = function () { + var this$1 = this; + + if (this.explicitlyCleared) { return } + this.explicitlyCleared = true; + for (var i = 0; i < this.markers.length; ++i) + { this$1.markers[i].clear(); } + signalLater(this, "clear"); + }; + + SharedTextMarker.prototype.find = function (side, lineObj) { + return this.primary.find(side, lineObj) + }; + eventMixin(SharedTextMarker); + + function markTextShared(doc, from, to, options, type) { + options = copyObj(options); + options.shared = false; + var markers = [markText(doc, from, to, options, type)], primary = markers[0]; + var widget = options.widgetNode; + linkedDocs(doc, function (doc) { + if (widget) { options.widgetNode = widget.cloneNode(true); } + markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type)); + for (var i = 0; i < doc.linked.length; ++i) + { if (doc.linked[i].isParent) { return } } + primary = lst(markers); + }); + return new SharedTextMarker(markers, primary) + } + + function findSharedMarkers(doc) { + return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), function (m) { return m.parent; }) + } + + function copySharedMarkers(doc, markers) { + for (var i = 0; i < markers.length; i++) { + var marker = markers[i], pos = marker.find(); + var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to); + if (cmp(mFrom, mTo)) { + var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type); + marker.markers.push(subMark); + subMark.parent = marker; + } + } + } + + function detachSharedMarkers(markers) { + var loop = function ( i ) { + var marker = markers[i], linked = [marker.primary.doc]; + linkedDocs(marker.primary.doc, function (d) { return linked.push(d); }); + for (var j = 0; j < marker.markers.length; j++) { + var subMarker = marker.markers[j]; + if (indexOf(linked, subMarker.doc) == -1) { + subMarker.parent = null; + marker.markers.splice(j--, 1); + } + } + }; + + for (var i = 0; i < markers.length; i++) loop( i ); + } + + var nextDocId = 0; + var Doc = function(text, mode, firstLine, lineSep, direction) { + if (!(this instanceof Doc)) { return new Doc(text, mode, firstLine, lineSep, direction) } + if (firstLine == null) { firstLine = 0; } + + BranchChunk.call(this, [new LeafChunk([new Line("", null)])]); + this.first = firstLine; + this.scrollTop = this.scrollLeft = 0; + this.cantEdit = false; + this.cleanGeneration = 1; + this.modeFrontier = this.highlightFrontier = firstLine; + var start = Pos(firstLine, 0); + this.sel = simpleSelection(start); + this.history = new History(null); + this.id = ++nextDocId; + this.modeOption = mode; + this.lineSep = lineSep; + this.direction = (direction == "rtl") ? "rtl" : "ltr"; + this.extend = false; + + if (typeof text == "string") { text = this.splitLines(text); } + updateDoc(this, {from: start, to: start, text: text}); + setSelection(this, simpleSelection(start), sel_dontScroll); + }; + + Doc.prototype = createObj(BranchChunk.prototype, { + constructor: Doc, + // Iterate over the document. Supports two forms -- with only one + // argument, it calls that for each line in the document. With + // three, it iterates over the range given by the first two (with + // the second being non-inclusive). + iter: function(from, to, op) { + if (op) { this.iterN(from - this.first, to - from, op); } + else { this.iterN(this.first, this.first + this.size, from); } + }, + + // Non-public interface for adding and removing lines. + insert: function(at, lines) { + var height = 0; + for (var i = 0; i < lines.length; ++i) { height += lines[i].height; } + this.insertInner(at - this.first, lines, height); + }, + remove: function(at, n) { this.removeInner(at - this.first, n); }, + + // From here, the methods are part of the public interface. Most + // are also available from CodeMirror (editor) instances. + + getValue: function(lineSep) { + var lines = getLines(this, this.first, this.first + this.size); + if (lineSep === false) { return lines } + return lines.join(lineSep || this.lineSeparator()) + }, + setValue: docMethodOp(function(code) { + var top = Pos(this.first, 0), last = this.first + this.size - 1; + makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length), + text: this.splitLines(code), origin: "setValue", full: true}, true); + if (this.cm) { scrollToCoords(this.cm, 0, 0); } + setSelection(this, simpleSelection(top), sel_dontScroll); + }), + replaceRange: function(code, from, to, origin) { + from = clipPos(this, from); + to = to ? clipPos(this, to) : from; + replaceRange(this, code, from, to, origin); + }, + getRange: function(from, to, lineSep) { + var lines = getBetween(this, clipPos(this, from), clipPos(this, to)); + if (lineSep === false) { return lines } + return lines.join(lineSep || this.lineSeparator()) + }, + + getLine: function(line) {var l = this.getLineHandle(line); return l && l.text}, + + getLineHandle: function(line) {if (isLine(this, line)) { return getLine(this, line) }}, + getLineNumber: function(line) {return lineNo(line)}, + + getLineHandleVisualStart: function(line) { + if (typeof line == "number") { line = getLine(this, line); } + return visualLine(line) + }, + + lineCount: function() {return this.size}, + firstLine: function() {return this.first}, + lastLine: function() {return this.first + this.size - 1}, + + clipPos: function(pos) {return clipPos(this, pos)}, + + getCursor: function(start) { + var range$$1 = this.sel.primary(), pos; + if (start == null || start == "head") { pos = range$$1.head; } + else if (start == "anchor") { pos = range$$1.anchor; } + else if (start == "end" || start == "to" || start === false) { pos = range$$1.to(); } + else { pos = range$$1.from(); } + return pos + }, + listSelections: function() { return this.sel.ranges }, + somethingSelected: function() {return this.sel.somethingSelected()}, + + setCursor: docMethodOp(function(line, ch, options) { + setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options); + }), + setSelection: docMethodOp(function(anchor, head, options) { + setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options); + }), + extendSelection: docMethodOp(function(head, other, options) { + extendSelection(this, clipPos(this, head), other && clipPos(this, other), options); + }), + extendSelections: docMethodOp(function(heads, options) { + extendSelections(this, clipPosArray(this, heads), options); + }), + extendSelectionsBy: docMethodOp(function(f, options) { + var heads = map(this.sel.ranges, f); + extendSelections(this, clipPosArray(this, heads), options); + }), + setSelections: docMethodOp(function(ranges, primary, options) { + var this$1 = this; + + if (!ranges.length) { return } + var out = []; + for (var i = 0; i < ranges.length; i++) + { out[i] = new Range(clipPos(this$1, ranges[i].anchor), + clipPos(this$1, ranges[i].head)); } + if (primary == null) { primary = Math.min(ranges.length - 1, this.sel.primIndex); } + setSelection(this, normalizeSelection(this.cm, out, primary), options); + }), + addSelection: docMethodOp(function(anchor, head, options) { + var ranges = this.sel.ranges.slice(0); + ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor))); + setSelection(this, normalizeSelection(this.cm, ranges, ranges.length - 1), options); + }), + + getSelection: function(lineSep) { + var this$1 = this; + + var ranges = this.sel.ranges, lines; + for (var i = 0; i < ranges.length; i++) { + var sel = getBetween(this$1, ranges[i].from(), ranges[i].to()); + lines = lines ? lines.concat(sel) : sel; + } + if (lineSep === false) { return lines } + else { return lines.join(lineSep || this.lineSeparator()) } + }, + getSelections: function(lineSep) { + var this$1 = this; + + var parts = [], ranges = this.sel.ranges; + for (var i = 0; i < ranges.length; i++) { + var sel = getBetween(this$1, ranges[i].from(), ranges[i].to()); + if (lineSep !== false) { sel = sel.join(lineSep || this$1.lineSeparator()); } + parts[i] = sel; + } + return parts + }, + replaceSelection: function(code, collapse, origin) { + var dup = []; + for (var i = 0; i < this.sel.ranges.length; i++) + { dup[i] = code; } + this.replaceSelections(dup, collapse, origin || "+input"); + }, + replaceSelections: docMethodOp(function(code, collapse, origin) { + var this$1 = this; + + var changes = [], sel = this.sel; + for (var i = 0; i < sel.ranges.length; i++) { + var range$$1 = sel.ranges[i]; + changes[i] = {from: range$$1.from(), to: range$$1.to(), text: this$1.splitLines(code[i]), origin: origin}; + } + var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse); + for (var i$1 = changes.length - 1; i$1 >= 0; i$1--) + { makeChange(this$1, changes[i$1]); } + if (newSel) { setSelectionReplaceHistory(this, newSel); } + else if (this.cm) { ensureCursorVisible(this.cm); } + }), + undo: docMethodOp(function() {makeChangeFromHistory(this, "undo");}), + redo: docMethodOp(function() {makeChangeFromHistory(this, "redo");}), + undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true);}), + redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true);}), + + setExtending: function(val) {this.extend = val;}, + getExtending: function() {return this.extend}, + + historySize: function() { + var hist = this.history, done = 0, undone = 0; + for (var i = 0; i < hist.done.length; i++) { if (!hist.done[i].ranges) { ++done; } } + for (var i$1 = 0; i$1 < hist.undone.length; i$1++) { if (!hist.undone[i$1].ranges) { ++undone; } } + return {undo: done, redo: undone} + }, + clearHistory: function() {this.history = new History(this.history.maxGeneration);}, + + markClean: function() { + this.cleanGeneration = this.changeGeneration(true); + }, + changeGeneration: function(forceSplit) { + if (forceSplit) + { this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null; } + return this.history.generation + }, + isClean: function (gen) { + return this.history.generation == (gen || this.cleanGeneration) + }, + + getHistory: function() { + return {done: copyHistoryArray(this.history.done), + undone: copyHistoryArray(this.history.undone)} + }, + setHistory: function(histData) { + var hist = this.history = new History(this.history.maxGeneration); + hist.done = copyHistoryArray(histData.done.slice(0), null, true); + hist.undone = copyHistoryArray(histData.undone.slice(0), null, true); + }, + + setGutterMarker: docMethodOp(function(line, gutterID, value) { + return changeLine(this, line, "gutter", function (line) { + var markers = line.gutterMarkers || (line.gutterMarkers = {}); + markers[gutterID] = value; + if (!value && isEmpty(markers)) { line.gutterMarkers = null; } + return true + }) + }), + + clearGutter: docMethodOp(function(gutterID) { + var this$1 = this; + + this.iter(function (line) { + if (line.gutterMarkers && line.gutterMarkers[gutterID]) { + changeLine(this$1, line, "gutter", function () { + line.gutterMarkers[gutterID] = null; + if (isEmpty(line.gutterMarkers)) { line.gutterMarkers = null; } + return true + }); + } + }); + }), + + lineInfo: function(line) { + var n; + if (typeof line == "number") { + if (!isLine(this, line)) { return null } + n = line; + line = getLine(this, line); + if (!line) { return null } + } else { + n = lineNo(line); + if (n == null) { return null } + } + return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers, + textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass, + widgets: line.widgets} + }, + + addLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function (line) { + var prop = where == "text" ? "textClass" + : where == "background" ? "bgClass" + : where == "gutter" ? "gutterClass" : "wrapClass"; + if (!line[prop]) { line[prop] = cls; } + else if (classTest(cls).test(line[prop])) { return false } + else { line[prop] += " " + cls; } + return true + }) + }), + removeLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function (line) { + var prop = where == "text" ? "textClass" + : where == "background" ? "bgClass" + : where == "gutter" ? "gutterClass" : "wrapClass"; + var cur = line[prop]; + if (!cur) { return false } + else if (cls == null) { line[prop] = null; } + else { + var found = cur.match(classTest(cls)); + if (!found) { return false } + var end = found.index + found[0].length; + line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null; + } + return true + }) + }), + + addLineWidget: docMethodOp(function(handle, node, options) { + return addLineWidget(this, handle, node, options) + }), + removeLineWidget: function(widget) { widget.clear(); }, + + markText: function(from, to, options) { + return markText(this, clipPos(this, from), clipPos(this, to), options, options && options.type || "range") + }, + setBookmark: function(pos, options) { + var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), + insertLeft: options && options.insertLeft, + clearWhenEmpty: false, shared: options && options.shared, + handleMouseEvents: options && options.handleMouseEvents}; + pos = clipPos(this, pos); + return markText(this, pos, pos, realOpts, "bookmark") + }, + findMarksAt: function(pos) { + pos = clipPos(this, pos); + var markers = [], spans = getLine(this, pos.line).markedSpans; + if (spans) { for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if ((span.from == null || span.from <= pos.ch) && + (span.to == null || span.to >= pos.ch)) + { markers.push(span.marker.parent || span.marker); } + } } + return markers + }, + findMarks: function(from, to, filter) { + from = clipPos(this, from); to = clipPos(this, to); + var found = [], lineNo$$1 = from.line; + this.iter(from.line, to.line + 1, function (line) { + var spans = line.markedSpans; + if (spans) { for (var i = 0; i < spans.length; i++) { + var span = spans[i]; + if (!(span.to != null && lineNo$$1 == from.line && from.ch >= span.to || + span.from == null && lineNo$$1 != from.line || + span.from != null && lineNo$$1 == to.line && span.from >= to.ch) && + (!filter || filter(span.marker))) + { found.push(span.marker.parent || span.marker); } + } } + ++lineNo$$1; + }); + return found + }, + getAllMarks: function() { + var markers = []; + this.iter(function (line) { + var sps = line.markedSpans; + if (sps) { for (var i = 0; i < sps.length; ++i) + { if (sps[i].from != null) { markers.push(sps[i].marker); } } } + }); + return markers + }, + + posFromIndex: function(off) { + var ch, lineNo$$1 = this.first, sepSize = this.lineSeparator().length; + this.iter(function (line) { + var sz = line.text.length + sepSize; + if (sz > off) { ch = off; return true } + off -= sz; + ++lineNo$$1; + }); + return clipPos(this, Pos(lineNo$$1, ch)) + }, + indexFromPos: function (coords) { + coords = clipPos(this, coords); + var index = coords.ch; + if (coords.line < this.first || coords.ch < 0) { return 0 } + var sepSize = this.lineSeparator().length; + this.iter(this.first, coords.line, function (line) { // iter aborts when callback returns a truthy value + index += line.text.length + sepSize; + }); + return index + }, + + copy: function(copyHistory) { + var doc = new Doc(getLines(this, this.first, this.first + this.size), + this.modeOption, this.first, this.lineSep, this.direction); + doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft; + doc.sel = this.sel; + doc.extend = false; + if (copyHistory) { + doc.history.undoDepth = this.history.undoDepth; + doc.setHistory(this.getHistory()); + } + return doc + }, + + linkedDoc: function(options) { + if (!options) { options = {}; } + var from = this.first, to = this.first + this.size; + if (options.from != null && options.from > from) { from = options.from; } + if (options.to != null && options.to < to) { to = options.to; } + var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from, this.lineSep, this.direction); + if (options.sharedHist) { copy.history = this.history + ; }(this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist}); + copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}]; + copySharedMarkers(copy, findSharedMarkers(this)); + return copy + }, + unlinkDoc: function(other) { + var this$1 = this; + + if (other instanceof CodeMirror) { other = other.doc; } + if (this.linked) { for (var i = 0; i < this.linked.length; ++i) { + var link = this$1.linked[i]; + if (link.doc != other) { continue } + this$1.linked.splice(i, 1); + other.unlinkDoc(this$1); + detachSharedMarkers(findSharedMarkers(this$1)); + break + } } + // If the histories were shared, split them again + if (other.history == this.history) { + var splitIds = [other.id]; + linkedDocs(other, function (doc) { return splitIds.push(doc.id); }, true); + other.history = new History(null); + other.history.done = copyHistoryArray(this.history.done, splitIds); + other.history.undone = copyHistoryArray(this.history.undone, splitIds); + } + }, + iterLinkedDocs: function(f) {linkedDocs(this, f);}, + + getMode: function() {return this.mode}, + getEditor: function() {return this.cm}, + + splitLines: function(str) { + if (this.lineSep) { return str.split(this.lineSep) } + return splitLinesAuto(str) + }, + lineSeparator: function() { return this.lineSep || "\n" }, + + setDirection: docMethodOp(function (dir) { + if (dir != "rtl") { dir = "ltr"; } + if (dir == this.direction) { return } + this.direction = dir; + this.iter(function (line) { return line.order = null; }); + if (this.cm) { directionChanged(this.cm); } + }) + }); + + // Public alias. + Doc.prototype.eachLine = Doc.prototype.iter; + + // Kludge to work around strange IE behavior where it'll sometimes + // re-fire a series of drag-related events right after the drop (#1551) + var lastDrop = 0; + + function onDrop(e) { + var cm = this; + clearDragCursor(cm); + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) + { return } + e_preventDefault(e); + if (ie) { lastDrop = +new Date; } + var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files; + if (!pos || cm.isReadOnly()) { return } + // Might be a file drop, in which case we simply extract the text + // and insert it. + if (files && files.length && window.FileReader && window.File) { + var n = files.length, text = Array(n), read = 0; + var loadFile = function (file, i) { + if (cm.options.allowDropFileTypes && + indexOf(cm.options.allowDropFileTypes, file.type) == -1) + { return } + + var reader = new FileReader; + reader.onload = operation(cm, function () { + var content = reader.result; + if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) { content = ""; } + text[i] = content; + if (++read == n) { + pos = clipPos(cm.doc, pos); + var change = {from: pos, to: pos, + text: cm.doc.splitLines(text.join(cm.doc.lineSeparator())), + origin: "paste"}; + makeChange(cm.doc, change); + setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change))); + } + }); + reader.readAsText(file); + }; + for (var i = 0; i < n; ++i) { loadFile(files[i], i); } + } else { // Normal drop + // Don't do a replace if the drop happened inside of the selected text. + if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) { + cm.state.draggingText(e); + // Ensure the editor is re-focused + setTimeout(function () { return cm.display.input.focus(); }, 20); + return + } + try { + var text$1 = e.dataTransfer.getData("Text"); + if (text$1) { + var selected; + if (cm.state.draggingText && !cm.state.draggingText.copy) + { selected = cm.listSelections(); } + setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)); + if (selected) { for (var i$1 = 0; i$1 < selected.length; ++i$1) + { replaceRange(cm.doc, "", selected[i$1].anchor, selected[i$1].head, "drag"); } } + cm.replaceSelection(text$1, "around", "paste"); + cm.display.input.focus(); + } + } + catch(e){} + } + } + + function onDragStart(cm, e) { + if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return } + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) { return } + + e.dataTransfer.setData("Text", cm.getSelection()); + e.dataTransfer.effectAllowed = "copyMove"; + + // Use dummy image instead of default browsers image. + // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. + if (e.dataTransfer.setDragImage && !safari) { + var img = elt("img", null, null, "position: fixed; left: 0; top: 0;"); + img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; + if (presto) { + img.width = img.height = 1; + cm.display.wrapper.appendChild(img); + // Force a relayout, or Opera won't use our image for some obscure reason + img._top = img.offsetTop; + } + e.dataTransfer.setDragImage(img, 0, 0); + if (presto) { img.parentNode.removeChild(img); } + } + } + + function onDragOver(cm, e) { + var pos = posFromMouse(cm, e); + if (!pos) { return } + var frag = document.createDocumentFragment(); + drawSelectionCursor(cm, pos, frag); + if (!cm.display.dragCursor) { + cm.display.dragCursor = elt("div", null, "CodeMirror-cursors CodeMirror-dragcursors"); + cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursorDiv); + } + removeChildrenAndAdd(cm.display.dragCursor, frag); + } + + function clearDragCursor(cm) { + if (cm.display.dragCursor) { + cm.display.lineSpace.removeChild(cm.display.dragCursor); + cm.display.dragCursor = null; + } + } + + // These must be handled carefully, because naively registering a + // handler for each editor will cause the editors to never be + // garbage collected. + + function forEachCodeMirror(f) { + if (!document.getElementsByClassName) { return } + var byClass = document.getElementsByClassName("CodeMirror"), editors = []; + for (var i = 0; i < byClass.length; i++) { + var cm = byClass[i].CodeMirror; + if (cm) { editors.push(cm); } + } + if (editors.length) { editors[0].operation(function () { + for (var i = 0; i < editors.length; i++) { f(editors[i]); } + }); } + } + + var globalsRegistered = false; + function ensureGlobalHandlers() { + if (globalsRegistered) { return } + registerGlobalHandlers(); + globalsRegistered = true; + } + function registerGlobalHandlers() { + // When the window resizes, we need to refresh active editors. + var resizeTimer; + on(window, "resize", function () { + if (resizeTimer == null) { resizeTimer = setTimeout(function () { + resizeTimer = null; + forEachCodeMirror(onResize); + }, 100); } + }); + // When the window loses focus, we want to show the editor as blurred + on(window, "blur", function () { return forEachCodeMirror(onBlur); }); + } + // Called when the window resizes + function onResize(cm) { + var d = cm.display; + // Might be a text scaling operation, clear size caches. + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; + d.scrollbarsClipped = false; + cm.setSize(); + } + + var keyNames = { + 3: "Pause", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", + 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", + 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", + 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", + 106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 127: "Delete", 145: "ScrollLock", + 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", + 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete", + 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert" + }; + + // Number keys + for (var i = 0; i < 10; i++) { keyNames[i + 48] = keyNames[i + 96] = String(i); } + // Alphabetic keys + for (var i$1 = 65; i$1 <= 90; i$1++) { keyNames[i$1] = String.fromCharCode(i$1); } + // Function keys + for (var i$2 = 1; i$2 <= 12; i$2++) { keyNames[i$2 + 111] = keyNames[i$2 + 63235] = "F" + i$2; } + + var keyMap = {}; + + keyMap.basic = { + "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", + "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", + "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore", + "Tab": "defaultTab", "Shift-Tab": "indentAuto", + "Enter": "newlineAndIndent", "Insert": "toggleOverwrite", + "Esc": "singleSelection" + }; + // Note that the save and find-related commands aren't defined by + // default. User code or addons can define them. Unknown commands + // are simply ignored. + keyMap.pcDefault = { + "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", + "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown", + "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", + "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find", + "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", + "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", + "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection", + "fallthrough": "basic" + }; + // Very basic readline/emacs-style bindings, which are standard on Mac. + keyMap.emacsy = { + "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", + "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", + "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", + "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars", + "Ctrl-O": "openLine" + }; + keyMap.macDefault = { + "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", + "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", + "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore", + "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", + "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", + "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight", + "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd", + "fallthrough": ["basic", "emacsy"] + }; + keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; + + // KEYMAP DISPATCH + + function normalizeKeyName(name) { + var parts = name.split(/-(?!$)/); + name = parts[parts.length - 1]; + var alt, ctrl, shift, cmd; + for (var i = 0; i < parts.length - 1; i++) { + var mod = parts[i]; + if (/^(cmd|meta|m)$/i.test(mod)) { cmd = true; } + else if (/^a(lt)?$/i.test(mod)) { alt = true; } + else if (/^(c|ctrl|control)$/i.test(mod)) { ctrl = true; } + else if (/^s(hift)?$/i.test(mod)) { shift = true; } + else { throw new Error("Unrecognized modifier name: " + mod) } + } + if (alt) { name = "Alt-" + name; } + if (ctrl) { name = "Ctrl-" + name; } + if (cmd) { name = "Cmd-" + name; } + if (shift) { name = "Shift-" + name; } + return name + } + + // This is a kludge to keep keymaps mostly working as raw objects + // (backwards compatibility) while at the same time support features + // like normalization and multi-stroke key bindings. It compiles a + // new normalized keymap, and then updates the old object to reflect + // this. + function normalizeKeyMap(keymap) { + var copy = {}; + for (var keyname in keymap) { if (keymap.hasOwnProperty(keyname)) { + var value = keymap[keyname]; + if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) { continue } + if (value == "...") { delete keymap[keyname]; continue } + + var keys = map(keyname.split(" "), normalizeKeyName); + for (var i = 0; i < keys.length; i++) { + var val = (void 0), name = (void 0); + if (i == keys.length - 1) { + name = keys.join(" "); + val = value; + } else { + name = keys.slice(0, i + 1).join(" "); + val = "..."; + } + var prev = copy[name]; + if (!prev) { copy[name] = val; } + else if (prev != val) { throw new Error("Inconsistent bindings for " + name) } + } + delete keymap[keyname]; + } } + for (var prop in copy) { keymap[prop] = copy[prop]; } + return keymap + } + + function lookupKey(key, map$$1, handle, context) { + map$$1 = getKeyMap(map$$1); + var found = map$$1.call ? map$$1.call(key, context) : map$$1[key]; + if (found === false) { return "nothing" } + if (found === "...") { return "multi" } + if (found != null && handle(found)) { return "handled" } + + if (map$$1.fallthrough) { + if (Object.prototype.toString.call(map$$1.fallthrough) != "[object Array]") + { return lookupKey(key, map$$1.fallthrough, handle, context) } + for (var i = 0; i < map$$1.fallthrough.length; i++) { + var result = lookupKey(key, map$$1.fallthrough[i], handle, context); + if (result) { return result } + } + } + } + + // Modifier key presses don't count as 'real' key presses for the + // purpose of keymap fallthrough. + function isModifierKey(value) { + var name = typeof value == "string" ? value : keyNames[value.keyCode]; + return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod" + } + + function addModifierNames(name, event, noShift) { + var base = name; + if (event.altKey && base != "Alt") { name = "Alt-" + name; } + if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") { name = "Ctrl-" + name; } + if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") { name = "Cmd-" + name; } + if (!noShift && event.shiftKey && base != "Shift") { name = "Shift-" + name; } + return name + } + + // Look up the name of a key as indicated by an event object. + function keyName(event, noShift) { + if (presto && event.keyCode == 34 && event["char"]) { return false } + var name = keyNames[event.keyCode]; + if (name == null || event.altGraphKey) { return false } + // Ctrl-ScrollLock has keyCode 3, same as Ctrl-Pause, + // so we'll use event.code when available (Chrome 48+, FF 38+, Safari 10.1+) + if (event.keyCode == 3 && event.code) { name = event.code; } + return addModifierNames(name, event, noShift) + } + + function getKeyMap(val) { + return typeof val == "string" ? keyMap[val] : val + } + + // Helper for deleting text near the selection(s), used to implement + // backspace, delete, and similar functionality. + function deleteNearSelection(cm, compute) { + var ranges = cm.doc.sel.ranges, kill = []; + // Build up a set of ranges to kill first, merging overlapping + // ranges. + for (var i = 0; i < ranges.length; i++) { + var toKill = compute(ranges[i]); + while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) { + var replaced = kill.pop(); + if (cmp(replaced.from, toKill.from) < 0) { + toKill.from = replaced.from; + break + } + } + kill.push(toKill); + } + // Next, remove those actual ranges. + runInOp(cm, function () { + for (var i = kill.length - 1; i >= 0; i--) + { replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete"); } + ensureCursorVisible(cm); + }); + } + + function moveCharLogically(line, ch, dir) { + var target = skipExtendingChars(line.text, ch + dir, dir); + return target < 0 || target > line.text.length ? null : target + } + + function moveLogically(line, start, dir) { + var ch = moveCharLogically(line, start.ch, dir); + return ch == null ? null : new Pos(start.line, ch, dir < 0 ? "after" : "before") + } + + function endOfLine(visually, cm, lineObj, lineNo, dir) { + if (visually) { + var order = getOrder(lineObj, cm.doc.direction); + if (order) { + var part = dir < 0 ? lst(order) : order[0]; + var moveInStorageOrder = (dir < 0) == (part.level == 1); + var sticky = moveInStorageOrder ? "after" : "before"; + var ch; + // With a wrapped rtl chunk (possibly spanning multiple bidi parts), + // it could be that the last bidi part is not on the last visual line, + // since visual lines contain content order-consecutive chunks. + // Thus, in rtl, we are looking for the first (content-order) character + // in the rtl chunk that is on the last line (that is, the same line + // as the last (content-order) character). + if (part.level > 0 || cm.doc.direction == "rtl") { + var prep = prepareMeasureForLine(cm, lineObj); + ch = dir < 0 ? lineObj.text.length - 1 : 0; + var targetTop = measureCharPrepared(cm, prep, ch).top; + ch = findFirst(function (ch) { return measureCharPrepared(cm, prep, ch).top == targetTop; }, (dir < 0) == (part.level == 1) ? part.from : part.to - 1, ch); + if (sticky == "before") { ch = moveCharLogically(lineObj, ch, 1); } + } else { ch = dir < 0 ? part.to : part.from; } + return new Pos(lineNo, ch, sticky) + } + } + return new Pos(lineNo, dir < 0 ? lineObj.text.length : 0, dir < 0 ? "before" : "after") + } + + function moveVisually(cm, line, start, dir) { + var bidi = getOrder(line, cm.doc.direction); + if (!bidi) { return moveLogically(line, start, dir) } + if (start.ch >= line.text.length) { + start.ch = line.text.length; + start.sticky = "before"; + } else if (start.ch <= 0) { + start.ch = 0; + start.sticky = "after"; + } + var partPos = getBidiPartAt(bidi, start.ch, start.sticky), part = bidi[partPos]; + if (cm.doc.direction == "ltr" && part.level % 2 == 0 && (dir > 0 ? part.to > start.ch : part.from < start.ch)) { + // Case 1: We move within an ltr part in an ltr editor. Even with wrapped lines, + // nothing interesting happens. + return moveLogically(line, start, dir) + } + + var mv = function (pos, dir) { return moveCharLogically(line, pos instanceof Pos ? pos.ch : pos, dir); }; + var prep; + var getWrappedLineExtent = function (ch) { + if (!cm.options.lineWrapping) { return {begin: 0, end: line.text.length} } + prep = prep || prepareMeasureForLine(cm, line); + return wrappedLineExtentChar(cm, line, prep, ch) + }; + var wrappedLineExtent = getWrappedLineExtent(start.sticky == "before" ? mv(start, -1) : start.ch); + + if (cm.doc.direction == "rtl" || part.level == 1) { + var moveInStorageOrder = (part.level == 1) == (dir < 0); + var ch = mv(start, moveInStorageOrder ? 1 : -1); + if (ch != null && (!moveInStorageOrder ? ch >= part.from && ch >= wrappedLineExtent.begin : ch <= part.to && ch <= wrappedLineExtent.end)) { + // Case 2: We move within an rtl part or in an rtl editor on the same visual line + var sticky = moveInStorageOrder ? "before" : "after"; + return new Pos(start.line, ch, sticky) + } + } + + // Case 3: Could not move within this bidi part in this visual line, so leave + // the current bidi part + + var searchInVisualLine = function (partPos, dir, wrappedLineExtent) { + var getRes = function (ch, moveInStorageOrder) { return moveInStorageOrder + ? new Pos(start.line, mv(ch, 1), "before") + : new Pos(start.line, ch, "after"); }; + + for (; partPos >= 0 && partPos < bidi.length; partPos += dir) { + var part = bidi[partPos]; + var moveInStorageOrder = (dir > 0) == (part.level != 1); + var ch = moveInStorageOrder ? wrappedLineExtent.begin : mv(wrappedLineExtent.end, -1); + if (part.from <= ch && ch < part.to) { return getRes(ch, moveInStorageOrder) } + ch = moveInStorageOrder ? part.from : mv(part.to, -1); + if (wrappedLineExtent.begin <= ch && ch < wrappedLineExtent.end) { return getRes(ch, moveInStorageOrder) } + } + }; + + // Case 3a: Look for other bidi parts on the same visual line + var res = searchInVisualLine(partPos + dir, dir, wrappedLineExtent); + if (res) { return res } + + // Case 3b: Look for other bidi parts on the next visual line + var nextCh = dir > 0 ? wrappedLineExtent.end : mv(wrappedLineExtent.begin, -1); + if (nextCh != null && !(dir > 0 && nextCh == line.text.length)) { + res = searchInVisualLine(dir > 0 ? 0 : bidi.length - 1, dir, getWrappedLineExtent(nextCh)); + if (res) { return res } + } + + // Case 4: Nowhere to move + return null + } + + // Commands are parameter-less actions that can be performed on an + // editor, mostly used for keybindings. + var commands = { + selectAll: selectAll, + singleSelection: function (cm) { return cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll); }, + killLine: function (cm) { return deleteNearSelection(cm, function (range) { + if (range.empty()) { + var len = getLine(cm.doc, range.head.line).text.length; + if (range.head.ch == len && range.head.line < cm.lastLine()) + { return {from: range.head, to: Pos(range.head.line + 1, 0)} } + else + { return {from: range.head, to: Pos(range.head.line, len)} } + } else { + return {from: range.from(), to: range.to()} + } + }); }, + deleteLine: function (cm) { return deleteNearSelection(cm, function (range) { return ({ + from: Pos(range.from().line, 0), + to: clipPos(cm.doc, Pos(range.to().line + 1, 0)) + }); }); }, + delLineLeft: function (cm) { return deleteNearSelection(cm, function (range) { return ({ + from: Pos(range.from().line, 0), to: range.from() + }); }); }, + delWrappedLineLeft: function (cm) { return deleteNearSelection(cm, function (range) { + var top = cm.charCoords(range.head, "div").top + 5; + var leftPos = cm.coordsChar({left: 0, top: top}, "div"); + return {from: leftPos, to: range.from()} + }); }, + delWrappedLineRight: function (cm) { return deleteNearSelection(cm, function (range) { + var top = cm.charCoords(range.head, "div").top + 5; + var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"); + return {from: range.from(), to: rightPos } + }); }, + undo: function (cm) { return cm.undo(); }, + redo: function (cm) { return cm.redo(); }, + undoSelection: function (cm) { return cm.undoSelection(); }, + redoSelection: function (cm) { return cm.redoSelection(); }, + goDocStart: function (cm) { return cm.extendSelection(Pos(cm.firstLine(), 0)); }, + goDocEnd: function (cm) { return cm.extendSelection(Pos(cm.lastLine())); }, + goLineStart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStart(cm, range.head.line); }, + {origin: "+move", bias: 1} + ); }, + goLineStartSmart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStartSmart(cm, range.head); }, + {origin: "+move", bias: 1} + ); }, + goLineEnd: function (cm) { return cm.extendSelectionsBy(function (range) { return lineEnd(cm, range.head.line); }, + {origin: "+move", bias: -1} + ); }, + goLineRight: function (cm) { return cm.extendSelectionsBy(function (range) { + var top = cm.cursorCoords(range.head, "div").top + 5; + return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div") + }, sel_move); }, + goLineLeft: function (cm) { return cm.extendSelectionsBy(function (range) { + var top = cm.cursorCoords(range.head, "div").top + 5; + return cm.coordsChar({left: 0, top: top}, "div") + }, sel_move); }, + goLineLeftSmart: function (cm) { return cm.extendSelectionsBy(function (range) { + var top = cm.cursorCoords(range.head, "div").top + 5; + var pos = cm.coordsChar({left: 0, top: top}, "div"); + if (pos.ch < cm.getLine(pos.line).search(/\S/)) { return lineStartSmart(cm, range.head) } + return pos + }, sel_move); }, + goLineUp: function (cm) { return cm.moveV(-1, "line"); }, + goLineDown: function (cm) { return cm.moveV(1, "line"); }, + goPageUp: function (cm) { return cm.moveV(-1, "page"); }, + goPageDown: function (cm) { return cm.moveV(1, "page"); }, + goCharLeft: function (cm) { return cm.moveH(-1, "char"); }, + goCharRight: function (cm) { return cm.moveH(1, "char"); }, + goColumnLeft: function (cm) { return cm.moveH(-1, "column"); }, + goColumnRight: function (cm) { return cm.moveH(1, "column"); }, + goWordLeft: function (cm) { return cm.moveH(-1, "word"); }, + goGroupRight: function (cm) { return cm.moveH(1, "group"); }, + goGroupLeft: function (cm) { return cm.moveH(-1, "group"); }, + goWordRight: function (cm) { return cm.moveH(1, "word"); }, + delCharBefore: function (cm) { return cm.deleteH(-1, "char"); }, + delCharAfter: function (cm) { return cm.deleteH(1, "char"); }, + delWordBefore: function (cm) { return cm.deleteH(-1, "word"); }, + delWordAfter: function (cm) { return cm.deleteH(1, "word"); }, + delGroupBefore: function (cm) { return cm.deleteH(-1, "group"); }, + delGroupAfter: function (cm) { return cm.deleteH(1, "group"); }, + indentAuto: function (cm) { return cm.indentSelection("smart"); }, + indentMore: function (cm) { return cm.indentSelection("add"); }, + indentLess: function (cm) { return cm.indentSelection("subtract"); }, + insertTab: function (cm) { return cm.replaceSelection("\t"); }, + insertSoftTab: function (cm) { + var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize; + for (var i = 0; i < ranges.length; i++) { + var pos = ranges[i].from(); + var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize); + spaces.push(spaceStr(tabSize - col % tabSize)); + } + cm.replaceSelections(spaces); + }, + defaultTab: function (cm) { + if (cm.somethingSelected()) { cm.indentSelection("add"); } + else { cm.execCommand("insertTab"); } + }, + // Swap the two chars left and right of each selection's head. + // Move cursor behind the two swapped characters afterwards. + // + // Doesn't consider line feeds a character. + // Doesn't scan more than one line above to find a character. + // Doesn't do anything on an empty line. + // Doesn't do anything with non-empty selections. + transposeChars: function (cm) { return runInOp(cm, function () { + var ranges = cm.listSelections(), newSel = []; + for (var i = 0; i < ranges.length; i++) { + if (!ranges[i].empty()) { continue } + var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text; + if (line) { + if (cur.ch == line.length) { cur = new Pos(cur.line, cur.ch - 1); } + if (cur.ch > 0) { + cur = new Pos(cur.line, cur.ch + 1); + cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2), + Pos(cur.line, cur.ch - 2), cur, "+transpose"); + } else if (cur.line > cm.doc.first) { + var prev = getLine(cm.doc, cur.line - 1).text; + if (prev) { + cur = new Pos(cur.line, 1); + cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() + + prev.charAt(prev.length - 1), + Pos(cur.line - 1, prev.length - 1), cur, "+transpose"); + } + } + } + newSel.push(new Range(cur, cur)); + } + cm.setSelections(newSel); + }); }, + newlineAndIndent: function (cm) { return runInOp(cm, function () { + var sels = cm.listSelections(); + for (var i = sels.length - 1; i >= 0; i--) + { cm.replaceRange(cm.doc.lineSeparator(), sels[i].anchor, sels[i].head, "+input"); } + sels = cm.listSelections(); + for (var i$1 = 0; i$1 < sels.length; i$1++) + { cm.indentLine(sels[i$1].from().line, null, true); } + ensureCursorVisible(cm); + }); }, + openLine: function (cm) { return cm.replaceSelection("\n", "start"); }, + toggleOverwrite: function (cm) { return cm.toggleOverwrite(); } + }; + + + function lineStart(cm, lineN) { + var line = getLine(cm.doc, lineN); + var visual = visualLine(line); + if (visual != line) { lineN = lineNo(visual); } + return endOfLine(true, cm, visual, lineN, 1) + } + function lineEnd(cm, lineN) { + var line = getLine(cm.doc, lineN); + var visual = visualLineEnd(line); + if (visual != line) { lineN = lineNo(visual); } + return endOfLine(true, cm, line, lineN, -1) + } + function lineStartSmart(cm, pos) { + var start = lineStart(cm, pos.line); + var line = getLine(cm.doc, start.line); + var order = getOrder(line, cm.doc.direction); + if (!order || order[0].level == 0) { + var firstNonWS = Math.max(0, line.text.search(/\S/)); + var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch; + return Pos(start.line, inWS ? 0 : firstNonWS, start.sticky) + } + return start + } + + // Run a handler that was bound to a key. + function doHandleBinding(cm, bound, dropShift) { + if (typeof bound == "string") { + bound = commands[bound]; + if (!bound) { return false } + } + // Ensure previous input has been read, so that the handler sees a + // consistent view of the document + cm.display.input.ensurePolled(); + var prevShift = cm.display.shift, done = false; + try { + if (cm.isReadOnly()) { cm.state.suppressEdits = true; } + if (dropShift) { cm.display.shift = false; } + done = bound(cm) != Pass; + } finally { + cm.display.shift = prevShift; + cm.state.suppressEdits = false; + } + return done + } + + function lookupKeyForEditor(cm, name, handle) { + for (var i = 0; i < cm.state.keyMaps.length; i++) { + var result = lookupKey(name, cm.state.keyMaps[i], handle, cm); + if (result) { return result } + } + return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm)) + || lookupKey(name, cm.options.keyMap, handle, cm) + } + + // Note that, despite the name, this function is also used to check + // for bound mouse clicks. + + var stopSeq = new Delayed; + + function dispatchKey(cm, name, e, handle) { + var seq = cm.state.keySeq; + if (seq) { + if (isModifierKey(name)) { return "handled" } + if (/\'$/.test(name)) + { cm.state.keySeq = null; } + else + { stopSeq.set(50, function () { + if (cm.state.keySeq == seq) { + cm.state.keySeq = null; + cm.display.input.reset(); + } + }); } + if (dispatchKeyInner(cm, seq + " " + name, e, handle)) { return true } + } + return dispatchKeyInner(cm, name, e, handle) + } + + function dispatchKeyInner(cm, name, e, handle) { + var result = lookupKeyForEditor(cm, name, handle); + + if (result == "multi") + { cm.state.keySeq = name; } + if (result == "handled") + { signalLater(cm, "keyHandled", cm, name, e); } + + if (result == "handled" || result == "multi") { + e_preventDefault(e); + restartBlink(cm); + } + + return !!result + } + + // Handle a key from the keydown event. + function handleKeyBinding(cm, e) { + var name = keyName(e, true); + if (!name) { return false } + + if (e.shiftKey && !cm.state.keySeq) { + // First try to resolve full name (including 'Shift-'). Failing + // that, see if there is a cursor-motion command (starting with + // 'go') bound to the keyname without 'Shift-'. + return dispatchKey(cm, "Shift-" + name, e, function (b) { return doHandleBinding(cm, b, true); }) + || dispatchKey(cm, name, e, function (b) { + if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) + { return doHandleBinding(cm, b) } + }) + } else { + return dispatchKey(cm, name, e, function (b) { return doHandleBinding(cm, b); }) + } + } + + // Handle a key from the keypress event + function handleCharBinding(cm, e, ch) { + return dispatchKey(cm, "'" + ch + "'", e, function (b) { return doHandleBinding(cm, b, true); }) + } + + var lastStoppedKey = null; + function onKeyDown(e) { + var cm = this; + cm.curOp.focus = activeElt(); + if (signalDOMEvent(cm, e)) { return } + // IE does strange things with escape. + if (ie && ie_version < 11 && e.keyCode == 27) { e.returnValue = false; } + var code = e.keyCode; + cm.display.shift = code == 16 || e.shiftKey; + var handled = handleKeyBinding(cm, e); + if (presto) { + lastStoppedKey = handled ? code : null; + // Opera has no cut event... we try to at least catch the key combo + if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey)) + { cm.replaceSelection("", null, "cut"); } + } + + // Turn mouse into crosshair when Alt is held on Mac. + if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className)) + { showCrossHair(cm); } + } + + function showCrossHair(cm) { + var lineDiv = cm.display.lineDiv; + addClass(lineDiv, "CodeMirror-crosshair"); + + function up(e) { + if (e.keyCode == 18 || !e.altKey) { + rmClass(lineDiv, "CodeMirror-crosshair"); + off(document, "keyup", up); + off(document, "mouseover", up); + } + } + on(document, "keyup", up); + on(document, "mouseover", up); + } + + function onKeyUp(e) { + if (e.keyCode == 16) { this.doc.sel.shift = false; } + signalDOMEvent(this, e); + } + + function onKeyPress(e) { + var cm = this; + if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) { return } + var keyCode = e.keyCode, charCode = e.charCode; + if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return} + if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) { return } + var ch = String.fromCharCode(charCode == null ? keyCode : charCode); + // Some browsers fire keypress events for backspace + if (ch == "\x08") { return } + if (handleCharBinding(cm, e, ch)) { return } + cm.display.input.onKeyPress(e); + } + + var DOUBLECLICK_DELAY = 400; + + var PastClick = function(time, pos, button) { + this.time = time; + this.pos = pos; + this.button = button; + }; + + PastClick.prototype.compare = function (time, pos, button) { + return this.time + DOUBLECLICK_DELAY > time && + cmp(pos, this.pos) == 0 && button == this.button + }; + + var lastClick, lastDoubleClick; + function clickRepeat(pos, button) { + var now = +new Date; + if (lastDoubleClick && lastDoubleClick.compare(now, pos, button)) { + lastClick = lastDoubleClick = null; + return "triple" + } else if (lastClick && lastClick.compare(now, pos, button)) { + lastDoubleClick = new PastClick(now, pos, button); + lastClick = null; + return "double" + } else { + lastClick = new PastClick(now, pos, button); + lastDoubleClick = null; + return "single" + } + } + + // A mouse down can be a single click, double click, triple click, + // start of selection drag, start of text drag, new cursor + // (ctrl-click), rectangle drag (alt-drag), or xwin + // middle-click-paste. Or it might be a click on something we should + // not interfere with, such as a scrollbar or widget. + function onMouseDown(e) { + var cm = this, display = cm.display; + if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouch()) { return } + display.input.ensurePolled(); + display.shift = e.shiftKey; + + if (eventInWidget(display, e)) { + if (!webkit) { + // Briefly turn off draggability, to allow widgets to do + // normal dragging things. + display.scroller.draggable = false; + setTimeout(function () { return display.scroller.draggable = true; }, 100); + } + return + } + if (clickInGutter(cm, e)) { return } + var pos = posFromMouse(cm, e), button = e_button(e), repeat = pos ? clickRepeat(pos, button) : "single"; + window.focus(); + + // #3261: make sure, that we're not starting a second selection + if (button == 1 && cm.state.selectingText) + { cm.state.selectingText(e); } + + if (pos && handleMappedButton(cm, button, pos, repeat, e)) { return } + + if (button == 1) { + if (pos) { leftButtonDown(cm, pos, repeat, e); } + else if (e_target(e) == display.scroller) { e_preventDefault(e); } + } else if (button == 2) { + if (pos) { extendSelection(cm.doc, pos); } + setTimeout(function () { return display.input.focus(); }, 20); + } else if (button == 3) { + if (captureRightClick) { cm.display.input.onContextMenu(e); } + else { delayBlurEvent(cm); } + } + } + + function handleMappedButton(cm, button, pos, repeat, event) { + var name = "Click"; + if (repeat == "double") { name = "Double" + name; } + else if (repeat == "triple") { name = "Triple" + name; } + name = (button == 1 ? "Left" : button == 2 ? "Middle" : "Right") + name; + + return dispatchKey(cm, addModifierNames(name, event), event, function (bound) { + if (typeof bound == "string") { bound = commands[bound]; } + if (!bound) { return false } + var done = false; + try { + if (cm.isReadOnly()) { cm.state.suppressEdits = true; } + done = bound(cm, pos) != Pass; + } finally { + cm.state.suppressEdits = false; + } + return done + }) + } + + function configureMouse(cm, repeat, event) { + var option = cm.getOption("configureMouse"); + var value = option ? option(cm, repeat, event) : {}; + if (value.unit == null) { + var rect = chromeOS ? event.shiftKey && event.metaKey : event.altKey; + value.unit = rect ? "rectangle" : repeat == "single" ? "char" : repeat == "double" ? "word" : "line"; + } + if (value.extend == null || cm.doc.extend) { value.extend = cm.doc.extend || event.shiftKey; } + if (value.addNew == null) { value.addNew = mac ? event.metaKey : event.ctrlKey; } + if (value.moveOnDrag == null) { value.moveOnDrag = !(mac ? event.altKey : event.ctrlKey); } + return value + } + + function leftButtonDown(cm, pos, repeat, event) { + if (ie) { setTimeout(bind(ensureFocus, cm), 0); } + else { cm.curOp.focus = activeElt(); } + + var behavior = configureMouse(cm, repeat, event); + + var sel = cm.doc.sel, contained; + if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() && + repeat == "single" && (contained = sel.contains(pos)) > -1 && + (cmp((contained = sel.ranges[contained]).from(), pos) < 0 || pos.xRel > 0) && + (cmp(contained.to(), pos) > 0 || pos.xRel < 0)) + { leftButtonStartDrag(cm, event, pos, behavior); } + else + { leftButtonSelect(cm, event, pos, behavior); } + } + + // Start a text drag. When it ends, see if any dragging actually + // happen, and treat as a click if it didn't. + function leftButtonStartDrag(cm, event, pos, behavior) { + var display = cm.display, moved = false; + var dragEnd = operation(cm, function (e) { + if (webkit) { display.scroller.draggable = false; } + cm.state.draggingText = false; + off(display.wrapper.ownerDocument, "mouseup", dragEnd); + off(display.wrapper.ownerDocument, "mousemove", mouseMove); + off(display.scroller, "dragstart", dragStart); + off(display.scroller, "drop", dragEnd); + if (!moved) { + e_preventDefault(e); + if (!behavior.addNew) + { extendSelection(cm.doc, pos, null, null, behavior.extend); } + // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081) + if (webkit || ie && ie_version == 9) + { setTimeout(function () {display.wrapper.ownerDocument.body.focus(); display.input.focus();}, 20); } + else + { display.input.focus(); } + } + }); + var mouseMove = function(e2) { + moved = moved || Math.abs(event.clientX - e2.clientX) + Math.abs(event.clientY - e2.clientY) >= 10; + }; + var dragStart = function () { return moved = true; }; + // Let the drag handler handle this. + if (webkit) { display.scroller.draggable = true; } + cm.state.draggingText = dragEnd; + dragEnd.copy = !behavior.moveOnDrag; + // IE's approach to draggable + if (display.scroller.dragDrop) { display.scroller.dragDrop(); } + on(display.wrapper.ownerDocument, "mouseup", dragEnd); + on(display.wrapper.ownerDocument, "mousemove", mouseMove); + on(display.scroller, "dragstart", dragStart); + on(display.scroller, "drop", dragEnd); + + delayBlurEvent(cm); + setTimeout(function () { return display.input.focus(); }, 20); + } + + function rangeForUnit(cm, pos, unit) { + if (unit == "char") { return new Range(pos, pos) } + if (unit == "word") { return cm.findWordAt(pos) } + if (unit == "line") { return new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))) } + var result = unit(cm, pos); + return new Range(result.from, result.to) + } + + // Normal selection, as opposed to text dragging. + function leftButtonSelect(cm, event, start, behavior) { + var display = cm.display, doc = cm.doc; + e_preventDefault(event); + + var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges; + if (behavior.addNew && !behavior.extend) { + ourIndex = doc.sel.contains(start); + if (ourIndex > -1) + { ourRange = ranges[ourIndex]; } + else + { ourRange = new Range(start, start); } + } else { + ourRange = doc.sel.primary(); + ourIndex = doc.sel.primIndex; + } + + if (behavior.unit == "rectangle") { + if (!behavior.addNew) { ourRange = new Range(start, start); } + start = posFromMouse(cm, event, true, true); + ourIndex = -1; + } else { + var range$$1 = rangeForUnit(cm, start, behavior.unit); + if (behavior.extend) + { ourRange = extendRange(ourRange, range$$1.anchor, range$$1.head, behavior.extend); } + else + { ourRange = range$$1; } + } + + if (!behavior.addNew) { + ourIndex = 0; + setSelection(doc, new Selection([ourRange], 0), sel_mouse); + startSel = doc.sel; + } else if (ourIndex == -1) { + ourIndex = ranges.length; + setSelection(doc, normalizeSelection(cm, ranges.concat([ourRange]), ourIndex), + {scroll: false, origin: "*mouse"}); + } else if (ranges.length > 1 && ranges[ourIndex].empty() && behavior.unit == "char" && !behavior.extend) { + setSelection(doc, normalizeSelection(cm, ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0), + {scroll: false, origin: "*mouse"}); + startSel = doc.sel; + } else { + replaceOneSelection(doc, ourIndex, ourRange, sel_mouse); + } + + var lastPos = start; + function extendTo(pos) { + if (cmp(lastPos, pos) == 0) { return } + lastPos = pos; + + if (behavior.unit == "rectangle") { + var ranges = [], tabSize = cm.options.tabSize; + var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize); + var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize); + var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol); + for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line)); + line <= end; line++) { + var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize); + if (left == right) + { ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))); } + else if (text.length > leftPos) + { ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))); } + } + if (!ranges.length) { ranges.push(new Range(start, start)); } + setSelection(doc, normalizeSelection(cm, startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex), + {origin: "*mouse", scroll: false}); + cm.scrollIntoView(pos); + } else { + var oldRange = ourRange; + var range$$1 = rangeForUnit(cm, pos, behavior.unit); + var anchor = oldRange.anchor, head; + if (cmp(range$$1.anchor, anchor) > 0) { + head = range$$1.head; + anchor = minPos(oldRange.from(), range$$1.anchor); + } else { + head = range$$1.anchor; + anchor = maxPos(oldRange.to(), range$$1.head); + } + var ranges$1 = startSel.ranges.slice(0); + ranges$1[ourIndex] = bidiSimplify(cm, new Range(clipPos(doc, anchor), head)); + setSelection(doc, normalizeSelection(cm, ranges$1, ourIndex), sel_mouse); + } + } + + var editorSize = display.wrapper.getBoundingClientRect(); + // Used to ensure timeout re-tries don't fire when another extend + // happened in the meantime (clearTimeout isn't reliable -- at + // least on Chrome, the timeouts still happen even when cleared, + // if the clear happens after their scheduled firing time). + var counter = 0; + + function extend(e) { + var curCount = ++counter; + var cur = posFromMouse(cm, e, true, behavior.unit == "rectangle"); + if (!cur) { return } + if (cmp(cur, lastPos) != 0) { + cm.curOp.focus = activeElt(); + extendTo(cur); + var visible = visibleLines(display, doc); + if (cur.line >= visible.to || cur.line < visible.from) + { setTimeout(operation(cm, function () {if (counter == curCount) { extend(e); }}), 150); } + } else { + var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0; + if (outside) { setTimeout(operation(cm, function () { + if (counter != curCount) { return } + display.scroller.scrollTop += outside; + extend(e); + }), 50); } + } + } + + function done(e) { + cm.state.selectingText = false; + counter = Infinity; + e_preventDefault(e); + display.input.focus(); + off(display.wrapper.ownerDocument, "mousemove", move); + off(display.wrapper.ownerDocument, "mouseup", up); + doc.history.lastSelOrigin = null; + } + + var move = operation(cm, function (e) { + if (e.buttons === 0 || !e_button(e)) { done(e); } + else { extend(e); } + }); + var up = operation(cm, done); + cm.state.selectingText = up; + on(display.wrapper.ownerDocument, "mousemove", move); + on(display.wrapper.ownerDocument, "mouseup", up); + } + + // Used when mouse-selecting to adjust the anchor to the proper side + // of a bidi jump depending on the visual position of the head. + function bidiSimplify(cm, range$$1) { + var anchor = range$$1.anchor; + var head = range$$1.head; + var anchorLine = getLine(cm.doc, anchor.line); + if (cmp(anchor, head) == 0 && anchor.sticky == head.sticky) { return range$$1 } + var order = getOrder(anchorLine); + if (!order) { return range$$1 } + var index = getBidiPartAt(order, anchor.ch, anchor.sticky), part = order[index]; + if (part.from != anchor.ch && part.to != anchor.ch) { return range$$1 } + var boundary = index + ((part.from == anchor.ch) == (part.level != 1) ? 0 : 1); + if (boundary == 0 || boundary == order.length) { return range$$1 } + + // Compute the relative visual position of the head compared to the + // anchor (<0 is to the left, >0 to the right) + var leftSide; + if (head.line != anchor.line) { + leftSide = (head.line - anchor.line) * (cm.doc.direction == "ltr" ? 1 : -1) > 0; + } else { + var headIndex = getBidiPartAt(order, head.ch, head.sticky); + var dir = headIndex - index || (head.ch - anchor.ch) * (part.level == 1 ? -1 : 1); + if (headIndex == boundary - 1 || headIndex == boundary) + { leftSide = dir < 0; } + else + { leftSide = dir > 0; } + } + + var usePart = order[boundary + (leftSide ? -1 : 0)]; + var from = leftSide == (usePart.level == 1); + var ch = from ? usePart.from : usePart.to, sticky = from ? "after" : "before"; + return anchor.ch == ch && anchor.sticky == sticky ? range$$1 : new Range(new Pos(anchor.line, ch, sticky), head) + } + + + // Determines whether an event happened in the gutter, and fires the + // handlers for the corresponding event. + function gutterEvent(cm, e, type, prevent) { + var mX, mY; + if (e.touches) { + mX = e.touches[0].clientX; + mY = e.touches[0].clientY; + } else { + try { mX = e.clientX; mY = e.clientY; } + catch(e) { return false } + } + if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) { return false } + if (prevent) { e_preventDefault(e); } + + var display = cm.display; + var lineBox = display.lineDiv.getBoundingClientRect(); + + if (mY > lineBox.bottom || !hasHandler(cm, type)) { return e_defaultPrevented(e) } + mY -= lineBox.top - display.viewOffset; + + for (var i = 0; i < cm.options.gutters.length; ++i) { + var g = display.gutters.childNodes[i]; + if (g && g.getBoundingClientRect().right >= mX) { + var line = lineAtHeight(cm.doc, mY); + var gutter = cm.options.gutters[i]; + signal(cm, type, cm, line, gutter, e); + return e_defaultPrevented(e) + } + } + } + + function clickInGutter(cm, e) { + return gutterEvent(cm, e, "gutterClick", true) + } + + // CONTEXT MENU HANDLING + + // To make the context menu work, we need to briefly unhide the + // textarea (making it as unobtrusive as possible) to let the + // right-click take effect on it. + function onContextMenu(cm, e) { + if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) { return } + if (signalDOMEvent(cm, e, "contextmenu")) { return } + if (!captureRightClick) { cm.display.input.onContextMenu(e); } + } + + function contextMenuInGutter(cm, e) { + if (!hasHandler(cm, "gutterContextMenu")) { return false } + return gutterEvent(cm, e, "gutterContextMenu", false) + } + + function themeChanged(cm) { + cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + + cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-"); + clearCaches(cm); + } + + var Init = {toString: function(){return "CodeMirror.Init"}}; + + var defaults = {}; + var optionHandlers = {}; + + function defineOptions(CodeMirror) { + var optionHandlers = CodeMirror.optionHandlers; + + function option(name, deflt, handle, notOnInit) { + CodeMirror.defaults[name] = deflt; + if (handle) { optionHandlers[name] = + notOnInit ? function (cm, val, old) {if (old != Init) { handle(cm, val, old); }} : handle; } + } + + CodeMirror.defineOption = option; + + // Passed to option handlers when there is no old value. + CodeMirror.Init = Init; + + // These two are, on init, called from the constructor because they + // have to be initialized before the editor can start at all. + option("value", "", function (cm, val) { return cm.setValue(val); }, true); + option("mode", null, function (cm, val) { + cm.doc.modeOption = val; + loadMode(cm); + }, true); + + option("indentUnit", 2, loadMode, true); + option("indentWithTabs", false); + option("smartIndent", true); + option("tabSize", 4, function (cm) { + resetModeState(cm); + clearCaches(cm); + regChange(cm); + }, true); + + option("lineSeparator", null, function (cm, val) { + cm.doc.lineSep = val; + if (!val) { return } + var newBreaks = [], lineNo = cm.doc.first; + cm.doc.iter(function (line) { + for (var pos = 0;;) { + var found = line.text.indexOf(val, pos); + if (found == -1) { break } + pos = found + val.length; + newBreaks.push(Pos(lineNo, found)); + } + lineNo++; + }); + for (var i = newBreaks.length - 1; i >= 0; i--) + { replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length)); } + }); + option("specialChars", /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff]/g, function (cm, val, old) { + cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g"); + if (old != Init) { cm.refresh(); } + }); + option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function (cm) { return cm.refresh(); }, true); + option("electricChars", true); + option("inputStyle", mobile ? "contenteditable" : "textarea", function () { + throw new Error("inputStyle can not (yet) be changed in a running editor") // FIXME + }, true); + option("spellcheck", false, function (cm, val) { return cm.getInputField().spellcheck = val; }, true); + option("autocorrect", false, function (cm, val) { return cm.getInputField().autocorrect = val; }, true); + option("autocapitalize", false, function (cm, val) { return cm.getInputField().autocapitalize = val; }, true); + option("rtlMoveVisually", !windows); + option("wholeLineUpdateBefore", true); + + option("theme", "default", function (cm) { + themeChanged(cm); + guttersChanged(cm); + }, true); + option("keyMap", "default", function (cm, val, old) { + var next = getKeyMap(val); + var prev = old != Init && getKeyMap(old); + if (prev && prev.detach) { prev.detach(cm, next); } + if (next.attach) { next.attach(cm, prev || null); } + }); + option("extraKeys", null); + option("configureMouse", null); + + option("lineWrapping", false, wrappingChanged, true); + option("gutters", [], function (cm) { + setGuttersForLineNumbers(cm.options); + guttersChanged(cm); + }, true); + option("fixedGutter", true, function (cm, val) { + cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0"; + cm.refresh(); + }, true); + option("coverGutterNextToScrollbar", false, function (cm) { return updateScrollbars(cm); }, true); + option("scrollbarStyle", "native", function (cm) { + initScrollbars(cm); + updateScrollbars(cm); + cm.display.scrollbars.setScrollTop(cm.doc.scrollTop); + cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft); + }, true); + option("lineNumbers", false, function (cm) { + setGuttersForLineNumbers(cm.options); + guttersChanged(cm); + }, true); + option("firstLineNumber", 1, guttersChanged, true); + option("lineNumberFormatter", function (integer) { return integer; }, guttersChanged, true); + option("showCursorWhenSelecting", false, updateSelection, true); + + option("resetSelectionOnContextMenu", true); + option("lineWiseCopyCut", true); + option("pasteLinesPerSelection", true); + option("selectionsMayTouch", false); + + option("readOnly", false, function (cm, val) { + if (val == "nocursor") { + onBlur(cm); + cm.display.input.blur(); + } + cm.display.input.readOnlyChanged(val); + }); + option("disableInput", false, function (cm, val) {if (!val) { cm.display.input.reset(); }}, true); + option("dragDrop", true, dragDropChanged); + option("allowDropFileTypes", null); + + option("cursorBlinkRate", 530); + option("cursorScrollMargin", 0); + option("cursorHeight", 1, updateSelection, true); + option("singleCursorHeightPerLine", true, updateSelection, true); + option("workTime", 100); + option("workDelay", 100); + option("flattenSpans", true, resetModeState, true); + option("addModeClass", false, resetModeState, true); + option("pollInterval", 100); + option("undoDepth", 200, function (cm, val) { return cm.doc.history.undoDepth = val; }); + option("historyEventDelay", 1250); + option("viewportMargin", 10, function (cm) { return cm.refresh(); }, true); + option("maxHighlightLength", 10000, resetModeState, true); + option("moveInputWithCursor", true, function (cm, val) { + if (!val) { cm.display.input.resetPosition(); } + }); + + option("tabindex", null, function (cm, val) { return cm.display.input.getField().tabIndex = val || ""; }); + option("autofocus", null); + option("direction", "ltr", function (cm, val) { return cm.doc.setDirection(val); }, true); + option("phrases", null); + } + + function guttersChanged(cm) { + updateGutters(cm); + regChange(cm); + alignHorizontally(cm); + } + + function dragDropChanged(cm, value, old) { + var wasOn = old && old != Init; + if (!value != !wasOn) { + var funcs = cm.display.dragFunctions; + var toggle = value ? on : off; + toggle(cm.display.scroller, "dragstart", funcs.start); + toggle(cm.display.scroller, "dragenter", funcs.enter); + toggle(cm.display.scroller, "dragover", funcs.over); + toggle(cm.display.scroller, "dragleave", funcs.leave); + toggle(cm.display.scroller, "drop", funcs.drop); + } + } + + function wrappingChanged(cm) { + if (cm.options.lineWrapping) { + addClass(cm.display.wrapper, "CodeMirror-wrap"); + cm.display.sizer.style.minWidth = ""; + cm.display.sizerWidth = null; + } else { + rmClass(cm.display.wrapper, "CodeMirror-wrap"); + findMaxLine(cm); + } + estimateLineHeights(cm); + regChange(cm); + clearCaches(cm); + setTimeout(function () { return updateScrollbars(cm); }, 100); + } + + // A CodeMirror instance represents an editor. This is the object + // that user code is usually dealing with. + + function CodeMirror(place, options) { + var this$1 = this; + + if (!(this instanceof CodeMirror)) { return new CodeMirror(place, options) } + + this.options = options = options ? copyObj(options) : {}; + // Determine effective options based on given values and defaults. + copyObj(defaults, options, false); + setGuttersForLineNumbers(options); + + var doc = options.value; + if (typeof doc == "string") { doc = new Doc(doc, options.mode, null, options.lineSeparator, options.direction); } + else if (options.mode) { doc.modeOption = options.mode; } + this.doc = doc; + + var input = new CodeMirror.inputStyles[options.inputStyle](this); + var display = this.display = new Display(place, doc, input); + display.wrapper.CodeMirror = this; + updateGutters(this); + themeChanged(this); + if (options.lineWrapping) + { this.display.wrapper.className += " CodeMirror-wrap"; } + initScrollbars(this); + + this.state = { + keyMaps: [], // stores maps added by addKeyMap + overlays: [], // highlighting overlays, as added by addOverlay + modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info + overwrite: false, + delayingBlurEvent: false, + focused: false, + suppressEdits: false, // used to disable editing during key handlers when in readOnly mode + pasteIncoming: -1, cutIncoming: -1, // help recognize paste/cut edits in input.poll + selectingText: false, + draggingText: false, + highlight: new Delayed(), // stores highlight worker timeout + keySeq: null, // Unfinished key sequence + specialChars: null + }; + + if (options.autofocus && !mobile) { display.input.focus(); } + + // Override magic textarea content restore that IE sometimes does + // on our hidden textarea on reload + if (ie && ie_version < 11) { setTimeout(function () { return this$1.display.input.reset(true); }, 20); } + + registerEventHandlers(this); + ensureGlobalHandlers(); + + startOperation(this); + this.curOp.forceUpdate = true; + attachDoc(this, doc); + + if ((options.autofocus && !mobile) || this.hasFocus()) + { setTimeout(bind(onFocus, this), 20); } + else + { onBlur(this); } + + for (var opt in optionHandlers) { if (optionHandlers.hasOwnProperty(opt)) + { optionHandlers[opt](this$1, options[opt], Init); } } + maybeUpdateLineNumberWidth(this); + if (options.finishInit) { options.finishInit(this); } + for (var i = 0; i < initHooks.length; ++i) { initHooks[i](this$1); } + endOperation(this); + // Suppress optimizelegibility in Webkit, since it breaks text + // measuring on line wrapping boundaries. + if (webkit && options.lineWrapping && + getComputedStyle(display.lineDiv).textRendering == "optimizelegibility") + { display.lineDiv.style.textRendering = "auto"; } + } + + // The default configuration options. + CodeMirror.defaults = defaults; + // Functions to run when options are changed. + CodeMirror.optionHandlers = optionHandlers; + + // Attach the necessary event handlers when initializing the editor + function registerEventHandlers(cm) { + var d = cm.display; + on(d.scroller, "mousedown", operation(cm, onMouseDown)); + // Older IE's will not fire a second mousedown for a double click + if (ie && ie_version < 11) + { on(d.scroller, "dblclick", operation(cm, function (e) { + if (signalDOMEvent(cm, e)) { return } + var pos = posFromMouse(cm, e); + if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) { return } + e_preventDefault(e); + var word = cm.findWordAt(pos); + extendSelection(cm.doc, word.anchor, word.head); + })); } + else + { on(d.scroller, "dblclick", function (e) { return signalDOMEvent(cm, e) || e_preventDefault(e); }); } + // Some browsers fire contextmenu *after* opening the menu, at + // which point we can't mess with it anymore. Context menu is + // handled in onMouseDown for these browsers. + on(d.scroller, "contextmenu", function (e) { return onContextMenu(cm, e); }); + + // Used to suppress mouse event handling when a touch happens + var touchFinished, prevTouch = {end: 0}; + function finishTouch() { + if (d.activeTouch) { + touchFinished = setTimeout(function () { return d.activeTouch = null; }, 1000); + prevTouch = d.activeTouch; + prevTouch.end = +new Date; + } + } + function isMouseLikeTouchEvent(e) { + if (e.touches.length != 1) { return false } + var touch = e.touches[0]; + return touch.radiusX <= 1 && touch.radiusY <= 1 + } + function farAway(touch, other) { + if (other.left == null) { return true } + var dx = other.left - touch.left, dy = other.top - touch.top; + return dx * dx + dy * dy > 20 * 20 + } + on(d.scroller, "touchstart", function (e) { + if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e) && !clickInGutter(cm, e)) { + d.input.ensurePolled(); + clearTimeout(touchFinished); + var now = +new Date; + d.activeTouch = {start: now, moved: false, + prev: now - prevTouch.end <= 300 ? prevTouch : null}; + if (e.touches.length == 1) { + d.activeTouch.left = e.touches[0].pageX; + d.activeTouch.top = e.touches[0].pageY; + } + } + }); + on(d.scroller, "touchmove", function () { + if (d.activeTouch) { d.activeTouch.moved = true; } + }); + on(d.scroller, "touchend", function (e) { + var touch = d.activeTouch; + if (touch && !eventInWidget(d, e) && touch.left != null && + !touch.moved && new Date - touch.start < 300) { + var pos = cm.coordsChar(d.activeTouch, "page"), range; + if (!touch.prev || farAway(touch, touch.prev)) // Single tap + { range = new Range(pos, pos); } + else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap + { range = cm.findWordAt(pos); } + else // Triple tap + { range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))); } + cm.setSelection(range.anchor, range.head); + cm.focus(); + e_preventDefault(e); + } + finishTouch(); + }); + on(d.scroller, "touchcancel", finishTouch); + + // Sync scrolling between fake scrollbars and real scrollable + // area, ensure viewport is updated when scrolling. + on(d.scroller, "scroll", function () { + if (d.scroller.clientHeight) { + updateScrollTop(cm, d.scroller.scrollTop); + setScrollLeft(cm, d.scroller.scrollLeft, true); + signal(cm, "scroll", cm); + } + }); + + // Listen to wheel events in order to try and update the viewport on time. + on(d.scroller, "mousewheel", function (e) { return onScrollWheel(cm, e); }); + on(d.scroller, "DOMMouseScroll", function (e) { return onScrollWheel(cm, e); }); + + // Prevent wrapper from ever scrolling + on(d.wrapper, "scroll", function () { return d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }); + + d.dragFunctions = { + enter: function (e) {if (!signalDOMEvent(cm, e)) { e_stop(e); }}, + over: function (e) {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(e); }}, + start: function (e) { return onDragStart(cm, e); }, + drop: operation(cm, onDrop), + leave: function (e) {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm); }} + }; + + var inp = d.input.getField(); + on(inp, "keyup", function (e) { return onKeyUp.call(cm, e); }); + on(inp, "keydown", operation(cm, onKeyDown)); + on(inp, "keypress", operation(cm, onKeyPress)); + on(inp, "focus", function (e) { return onFocus(cm, e); }); + on(inp, "blur", function (e) { return onBlur(cm, e); }); + } + + var initHooks = []; + CodeMirror.defineInitHook = function (f) { return initHooks.push(f); }; + + // Indent the given line. The how parameter can be "smart", + // "add"/null, "subtract", or "prev". When aggressive is false + // (typically set to true for forced single-line indents), empty + // lines are not indented, and places where the mode returns Pass + // are left alone. + function indentLine(cm, n, how, aggressive) { + var doc = cm.doc, state; + if (how == null) { how = "add"; } + if (how == "smart") { + // Fall back to "prev" when the mode doesn't have an indentation + // method. + if (!doc.mode.indent) { how = "prev"; } + else { state = getContextBefore(cm, n).state; } + } + + var tabSize = cm.options.tabSize; + var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize); + if (line.stateAfter) { line.stateAfter = null; } + var curSpaceString = line.text.match(/^\s*/)[0], indentation; + if (!aggressive && !/\S/.test(line.text)) { + indentation = 0; + how = "not"; + } else if (how == "smart") { + indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text); + if (indentation == Pass || indentation > 150) { + if (!aggressive) { return } + how = "prev"; + } + } + if (how == "prev") { + if (n > doc.first) { indentation = countColumn(getLine(doc, n-1).text, null, tabSize); } + else { indentation = 0; } + } else if (how == "add") { + indentation = curSpace + cm.options.indentUnit; + } else if (how == "subtract") { + indentation = curSpace - cm.options.indentUnit; + } else if (typeof how == "number") { + indentation = curSpace + how; + } + indentation = Math.max(0, indentation); + + var indentString = "", pos = 0; + if (cm.options.indentWithTabs) + { for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";} } + if (pos < indentation) { indentString += spaceStr(indentation - pos); } + + if (indentString != curSpaceString) { + replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input"); + line.stateAfter = null; + return true + } else { + // Ensure that, if the cursor was in the whitespace at the start + // of the line, it is moved to the end of that space. + for (var i$1 = 0; i$1 < doc.sel.ranges.length; i$1++) { + var range = doc.sel.ranges[i$1]; + if (range.head.line == n && range.head.ch < curSpaceString.length) { + var pos$1 = Pos(n, curSpaceString.length); + replaceOneSelection(doc, i$1, new Range(pos$1, pos$1)); + break + } + } + } + } + + // This will be set to a {lineWise: bool, text: [string]} object, so + // that, when pasting, we know what kind of selections the copied + // text was made out of. + var lastCopied = null; + + function setLastCopied(newLastCopied) { + lastCopied = newLastCopied; + } + + function applyTextInput(cm, inserted, deleted, sel, origin) { + var doc = cm.doc; + cm.display.shift = false; + if (!sel) { sel = doc.sel; } + + var recent = +new Date - 200; + var paste = origin == "paste" || cm.state.pasteIncoming > recent; + var textLines = splitLinesAuto(inserted), multiPaste = null; + // When pasting N lines into N selections, insert one line per selection + if (paste && sel.ranges.length > 1) { + if (lastCopied && lastCopied.text.join("\n") == inserted) { + if (sel.ranges.length % lastCopied.text.length == 0) { + multiPaste = []; + for (var i = 0; i < lastCopied.text.length; i++) + { multiPaste.push(doc.splitLines(lastCopied.text[i])); } + } + } else if (textLines.length == sel.ranges.length && cm.options.pasteLinesPerSelection) { + multiPaste = map(textLines, function (l) { return [l]; }); + } + } + + var updateInput = cm.curOp.updateInput; + // Normal behavior is to insert the new text into every selection + for (var i$1 = sel.ranges.length - 1; i$1 >= 0; i$1--) { + var range$$1 = sel.ranges[i$1]; + var from = range$$1.from(), to = range$$1.to(); + if (range$$1.empty()) { + if (deleted && deleted > 0) // Handle deletion + { from = Pos(from.line, from.ch - deleted); } + else if (cm.state.overwrite && !paste) // Handle overwrite + { to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)); } + else if (paste && lastCopied && lastCopied.lineWise && lastCopied.text.join("\n") == inserted) + { from = to = Pos(from.line, 0); } + } + var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i$1 % multiPaste.length] : textLines, + origin: origin || (paste ? "paste" : cm.state.cutIncoming > recent ? "cut" : "+input")}; + makeChange(cm.doc, changeEvent); + signalLater(cm, "inputRead", cm, changeEvent); + } + if (inserted && !paste) + { triggerElectric(cm, inserted); } + + ensureCursorVisible(cm); + if (cm.curOp.updateInput < 2) { cm.curOp.updateInput = updateInput; } + cm.curOp.typing = true; + cm.state.pasteIncoming = cm.state.cutIncoming = -1; + } + + function handlePaste(e, cm) { + var pasted = e.clipboardData && e.clipboardData.getData("Text"); + if (pasted) { + e.preventDefault(); + if (!cm.isReadOnly() && !cm.options.disableInput) + { runInOp(cm, function () { return applyTextInput(cm, pasted, 0, null, "paste"); }); } + return true + } + } + + function triggerElectric(cm, inserted) { + // When an 'electric' character is inserted, immediately trigger a reindent + if (!cm.options.electricChars || !cm.options.smartIndent) { return } + var sel = cm.doc.sel; + + for (var i = sel.ranges.length - 1; i >= 0; i--) { + var range$$1 = sel.ranges[i]; + if (range$$1.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range$$1.head.line)) { continue } + var mode = cm.getModeAt(range$$1.head); + var indented = false; + if (mode.electricChars) { + for (var j = 0; j < mode.electricChars.length; j++) + { if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) { + indented = indentLine(cm, range$$1.head.line, "smart"); + break + } } + } else if (mode.electricInput) { + if (mode.electricInput.test(getLine(cm.doc, range$$1.head.line).text.slice(0, range$$1.head.ch))) + { indented = indentLine(cm, range$$1.head.line, "smart"); } + } + if (indented) { signalLater(cm, "electricInput", cm, range$$1.head.line); } + } + } + + function copyableRanges(cm) { + var text = [], ranges = []; + for (var i = 0; i < cm.doc.sel.ranges.length; i++) { + var line = cm.doc.sel.ranges[i].head.line; + var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)}; + ranges.push(lineRange); + text.push(cm.getRange(lineRange.anchor, lineRange.head)); + } + return {text: text, ranges: ranges} + } + + function disableBrowserMagic(field, spellcheck, autocorrect, autocapitalize) { + field.setAttribute("autocorrect", !!autocorrect); + field.setAttribute("autocapitalize", !!autocapitalize); + field.setAttribute("spellcheck", !!spellcheck); + } + + function hiddenTextarea() { + var te = elt("textarea", null, null, "position: absolute; bottom: -1em; padding: 0; width: 1px; height: 1em; outline: none"); + var div = elt("div", [te], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); + // The textarea is kept positioned near the cursor to prevent the + // fact that it'll be scrolled into view on input from scrolling + // our fake cursor out of view. On webkit, when wrap=off, paste is + // very slow. So make the area wide instead. + if (webkit) { te.style.width = "1000px"; } + else { te.setAttribute("wrap", "off"); } + // If border: 0; -- iOS fails to open keyboard (issue #1287) + if (ios) { te.style.border = "1px solid black"; } + disableBrowserMagic(te); + return div + } + + // The publicly visible API. Note that methodOp(f) means + // 'wrap f in an operation, performed on its `this` parameter'. + + // This is not the complete set of editor methods. Most of the + // methods defined on the Doc type are also injected into + // CodeMirror.prototype, for backwards compatibility and + // convenience. + + function addEditorMethods(CodeMirror) { + var optionHandlers = CodeMirror.optionHandlers; + + var helpers = CodeMirror.helpers = {}; + + CodeMirror.prototype = { + constructor: CodeMirror, + focus: function(){window.focus(); this.display.input.focus();}, + + setOption: function(option, value) { + var options = this.options, old = options[option]; + if (options[option] == value && option != "mode") { return } + options[option] = value; + if (optionHandlers.hasOwnProperty(option)) + { operation(this, optionHandlers[option])(this, value, old); } + signal(this, "optionChange", this, option); + }, + + getOption: function(option) {return this.options[option]}, + getDoc: function() {return this.doc}, + + addKeyMap: function(map$$1, bottom) { + this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map$$1)); + }, + removeKeyMap: function(map$$1) { + var maps = this.state.keyMaps; + for (var i = 0; i < maps.length; ++i) + { if (maps[i] == map$$1 || maps[i].name == map$$1) { + maps.splice(i, 1); + return true + } } + }, + + addOverlay: methodOp(function(spec, options) { + var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec); + if (mode.startState) { throw new Error("Overlays may not be stateful.") } + insertSorted(this.state.overlays, + {mode: mode, modeSpec: spec, opaque: options && options.opaque, + priority: (options && options.priority) || 0}, + function (overlay) { return overlay.priority; }); + this.state.modeGen++; + regChange(this); + }), + removeOverlay: methodOp(function(spec) { + var this$1 = this; + + var overlays = this.state.overlays; + for (var i = 0; i < overlays.length; ++i) { + var cur = overlays[i].modeSpec; + if (cur == spec || typeof spec == "string" && cur.name == spec) { + overlays.splice(i, 1); + this$1.state.modeGen++; + regChange(this$1); + return + } + } + }), + + indentLine: methodOp(function(n, dir, aggressive) { + if (typeof dir != "string" && typeof dir != "number") { + if (dir == null) { dir = this.options.smartIndent ? "smart" : "prev"; } + else { dir = dir ? "add" : "subtract"; } + } + if (isLine(this.doc, n)) { indentLine(this, n, dir, aggressive); } + }), + indentSelection: methodOp(function(how) { + var this$1 = this; + + var ranges = this.doc.sel.ranges, end = -1; + for (var i = 0; i < ranges.length; i++) { + var range$$1 = ranges[i]; + if (!range$$1.empty()) { + var from = range$$1.from(), to = range$$1.to(); + var start = Math.max(end, from.line); + end = Math.min(this$1.lastLine(), to.line - (to.ch ? 0 : 1)) + 1; + for (var j = start; j < end; ++j) + { indentLine(this$1, j, how); } + var newRanges = this$1.doc.sel.ranges; + if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0) + { replaceOneSelection(this$1.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll); } + } else if (range$$1.head.line > end) { + indentLine(this$1, range$$1.head.line, how, true); + end = range$$1.head.line; + if (i == this$1.doc.sel.primIndex) { ensureCursorVisible(this$1); } + } + } + }), + + // Fetch the parser token for a given character. Useful for hacks + // that want to inspect the mode state (say, for completion). + getTokenAt: function(pos, precise) { + return takeToken(this, pos, precise) + }, + + getLineTokens: function(line, precise) { + return takeToken(this, Pos(line), precise, true) + }, + + getTokenTypeAt: function(pos) { + pos = clipPos(this.doc, pos); + var styles = getLineStyles(this, getLine(this.doc, pos.line)); + var before = 0, after = (styles.length - 1) / 2, ch = pos.ch; + var type; + if (ch == 0) { type = styles[2]; } + else { for (;;) { + var mid = (before + after) >> 1; + if ((mid ? styles[mid * 2 - 1] : 0) >= ch) { after = mid; } + else if (styles[mid * 2 + 1] < ch) { before = mid + 1; } + else { type = styles[mid * 2 + 2]; break } + } } + var cut = type ? type.indexOf("overlay ") : -1; + return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1) + }, + + getModeAt: function(pos) { + var mode = this.doc.mode; + if (!mode.innerMode) { return mode } + return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode + }, + + getHelper: function(pos, type) { + return this.getHelpers(pos, type)[0] + }, + + getHelpers: function(pos, type) { + var this$1 = this; + + var found = []; + if (!helpers.hasOwnProperty(type)) { return found } + var help = helpers[type], mode = this.getModeAt(pos); + if (typeof mode[type] == "string") { + if (help[mode[type]]) { found.push(help[mode[type]]); } + } else if (mode[type]) { + for (var i = 0; i < mode[type].length; i++) { + var val = help[mode[type][i]]; + if (val) { found.push(val); } + } + } else if (mode.helperType && help[mode.helperType]) { + found.push(help[mode.helperType]); + } else if (help[mode.name]) { + found.push(help[mode.name]); + } + for (var i$1 = 0; i$1 < help._global.length; i$1++) { + var cur = help._global[i$1]; + if (cur.pred(mode, this$1) && indexOf(found, cur.val) == -1) + { found.push(cur.val); } + } + return found + }, + + getStateAfter: function(line, precise) { + var doc = this.doc; + line = clipLine(doc, line == null ? doc.first + doc.size - 1: line); + return getContextBefore(this, line + 1, precise).state + }, + + cursorCoords: function(start, mode) { + var pos, range$$1 = this.doc.sel.primary(); + if (start == null) { pos = range$$1.head; } + else if (typeof start == "object") { pos = clipPos(this.doc, start); } + else { pos = start ? range$$1.from() : range$$1.to(); } + return cursorCoords(this, pos, mode || "page") + }, + + charCoords: function(pos, mode) { + return charCoords(this, clipPos(this.doc, pos), mode || "page") + }, + + coordsChar: function(coords, mode) { + coords = fromCoordSystem(this, coords, mode || "page"); + return coordsChar(this, coords.left, coords.top) + }, + + lineAtHeight: function(height, mode) { + height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top; + return lineAtHeight(this.doc, height + this.display.viewOffset) + }, + heightAtLine: function(line, mode, includeWidgets) { + var end = false, lineObj; + if (typeof line == "number") { + var last = this.doc.first + this.doc.size - 1; + if (line < this.doc.first) { line = this.doc.first; } + else if (line > last) { line = last; end = true; } + lineObj = getLine(this.doc, line); + } else { + lineObj = line; + } + return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page", includeWidgets || end).top + + (end ? this.doc.height - heightAtLine(lineObj) : 0) + }, + + defaultTextHeight: function() { return textHeight(this.display) }, + defaultCharWidth: function() { return charWidth(this.display) }, + + getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo}}, + + addWidget: function(pos, node, scroll, vert, horiz) { + var display = this.display; + pos = cursorCoords(this, clipPos(this.doc, pos)); + var top = pos.bottom, left = pos.left; + node.style.position = "absolute"; + node.setAttribute("cm-ignore-events", "true"); + this.display.input.setUneditable(node); + display.sizer.appendChild(node); + if (vert == "over") { + top = pos.top; + } else if (vert == "above" || vert == "near") { + var vspace = Math.max(display.wrapper.clientHeight, this.doc.height), + hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth); + // Default to positioning above (if specified and possible); otherwise default to positioning below + if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight) + { top = pos.top - node.offsetHeight; } + else if (pos.bottom + node.offsetHeight <= vspace) + { top = pos.bottom; } + if (left + node.offsetWidth > hspace) + { left = hspace - node.offsetWidth; } + } + node.style.top = top + "px"; + node.style.left = node.style.right = ""; + if (horiz == "right") { + left = display.sizer.clientWidth - node.offsetWidth; + node.style.right = "0px"; + } else { + if (horiz == "left") { left = 0; } + else if (horiz == "middle") { left = (display.sizer.clientWidth - node.offsetWidth) / 2; } + node.style.left = left + "px"; + } + if (scroll) + { scrollIntoView(this, {left: left, top: top, right: left + node.offsetWidth, bottom: top + node.offsetHeight}); } + }, + + triggerOnKeyDown: methodOp(onKeyDown), + triggerOnKeyPress: methodOp(onKeyPress), + triggerOnKeyUp: onKeyUp, + triggerOnMouseDown: methodOp(onMouseDown), + + execCommand: function(cmd) { + if (commands.hasOwnProperty(cmd)) + { return commands[cmd].call(null, this) } + }, + + triggerElectric: methodOp(function(text) { triggerElectric(this, text); }), + + findPosH: function(from, amount, unit, visually) { + var this$1 = this; + + var dir = 1; + if (amount < 0) { dir = -1; amount = -amount; } + var cur = clipPos(this.doc, from); + for (var i = 0; i < amount; ++i) { + cur = findPosH(this$1.doc, cur, dir, unit, visually); + if (cur.hitSide) { break } + } + return cur + }, + + moveH: methodOp(function(dir, unit) { + var this$1 = this; + + this.extendSelectionsBy(function (range$$1) { + if (this$1.display.shift || this$1.doc.extend || range$$1.empty()) + { return findPosH(this$1.doc, range$$1.head, dir, unit, this$1.options.rtlMoveVisually) } + else + { return dir < 0 ? range$$1.from() : range$$1.to() } + }, sel_move); + }), + + deleteH: methodOp(function(dir, unit) { + var sel = this.doc.sel, doc = this.doc; + if (sel.somethingSelected()) + { doc.replaceSelection("", null, "+delete"); } + else + { deleteNearSelection(this, function (range$$1) { + var other = findPosH(doc, range$$1.head, dir, unit, false); + return dir < 0 ? {from: other, to: range$$1.head} : {from: range$$1.head, to: other} + }); } + }), + + findPosV: function(from, amount, unit, goalColumn) { + var this$1 = this; + + var dir = 1, x = goalColumn; + if (amount < 0) { dir = -1; amount = -amount; } + var cur = clipPos(this.doc, from); + for (var i = 0; i < amount; ++i) { + var coords = cursorCoords(this$1, cur, "div"); + if (x == null) { x = coords.left; } + else { coords.left = x; } + cur = findPosV(this$1, coords, dir, unit); + if (cur.hitSide) { break } + } + return cur + }, + + moveV: methodOp(function(dir, unit) { + var this$1 = this; + + var doc = this.doc, goals = []; + var collapse = !this.display.shift && !doc.extend && doc.sel.somethingSelected(); + doc.extendSelectionsBy(function (range$$1) { + if (collapse) + { return dir < 0 ? range$$1.from() : range$$1.to() } + var headPos = cursorCoords(this$1, range$$1.head, "div"); + if (range$$1.goalColumn != null) { headPos.left = range$$1.goalColumn; } + goals.push(headPos.left); + var pos = findPosV(this$1, headPos, dir, unit); + if (unit == "page" && range$$1 == doc.sel.primary()) + { addToScrollTop(this$1, charCoords(this$1, pos, "div").top - headPos.top); } + return pos + }, sel_move); + if (goals.length) { for (var i = 0; i < doc.sel.ranges.length; i++) + { doc.sel.ranges[i].goalColumn = goals[i]; } } + }), + + // Find the word at the given position (as returned by coordsChar). + findWordAt: function(pos) { + var doc = this.doc, line = getLine(doc, pos.line).text; + var start = pos.ch, end = pos.ch; + if (line) { + var helper = this.getHelper(pos, "wordChars"); + if ((pos.sticky == "before" || end == line.length) && start) { --start; } else { ++end; } + var startChar = line.charAt(start); + var check = isWordChar(startChar, helper) + ? function (ch) { return isWordChar(ch, helper); } + : /\s/.test(startChar) ? function (ch) { return /\s/.test(ch); } + : function (ch) { return (!/\s/.test(ch) && !isWordChar(ch)); }; + while (start > 0 && check(line.charAt(start - 1))) { --start; } + while (end < line.length && check(line.charAt(end))) { ++end; } + } + return new Range(Pos(pos.line, start), Pos(pos.line, end)) + }, + + toggleOverwrite: function(value) { + if (value != null && value == this.state.overwrite) { return } + if (this.state.overwrite = !this.state.overwrite) + { addClass(this.display.cursorDiv, "CodeMirror-overwrite"); } + else + { rmClass(this.display.cursorDiv, "CodeMirror-overwrite"); } + + signal(this, "overwriteToggle", this, this.state.overwrite); + }, + hasFocus: function() { return this.display.input.getField() == activeElt() }, + isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit) }, + + scrollTo: methodOp(function (x, y) { scrollToCoords(this, x, y); }), + getScrollInfo: function() { + var scroller = this.display.scroller; + return {left: scroller.scrollLeft, top: scroller.scrollTop, + height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight, + width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth, + clientHeight: displayHeight(this), clientWidth: displayWidth(this)} + }, + + scrollIntoView: methodOp(function(range$$1, margin) { + if (range$$1 == null) { + range$$1 = {from: this.doc.sel.primary().head, to: null}; + if (margin == null) { margin = this.options.cursorScrollMargin; } + } else if (typeof range$$1 == "number") { + range$$1 = {from: Pos(range$$1, 0), to: null}; + } else if (range$$1.from == null) { + range$$1 = {from: range$$1, to: null}; + } + if (!range$$1.to) { range$$1.to = range$$1.from; } + range$$1.margin = margin || 0; + + if (range$$1.from.line != null) { + scrollToRange(this, range$$1); + } else { + scrollToCoordsRange(this, range$$1.from, range$$1.to, range$$1.margin); + } + }), + + setSize: methodOp(function(width, height) { + var this$1 = this; + + var interpret = function (val) { return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; }; + if (width != null) { this.display.wrapper.style.width = interpret(width); } + if (height != null) { this.display.wrapper.style.height = interpret(height); } + if (this.options.lineWrapping) { clearLineMeasurementCache(this); } + var lineNo$$1 = this.display.viewFrom; + this.doc.iter(lineNo$$1, this.display.viewTo, function (line) { + if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) + { if (line.widgets[i].noHScroll) { regLineChange(this$1, lineNo$$1, "widget"); break } } } + ++lineNo$$1; + }); + this.curOp.forceUpdate = true; + signal(this, "refresh", this); + }), + + operation: function(f){return runInOp(this, f)}, + startOperation: function(){return startOperation(this)}, + endOperation: function(){return endOperation(this)}, + + refresh: methodOp(function() { + var oldHeight = this.display.cachedTextHeight; + regChange(this); + this.curOp.forceUpdate = true; + clearCaches(this); + scrollToCoords(this, this.doc.scrollLeft, this.doc.scrollTop); + updateGutterSpace(this); + if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5) + { estimateLineHeights(this); } + signal(this, "refresh", this); + }), + + swapDoc: methodOp(function(doc) { + var old = this.doc; + old.cm = null; + attachDoc(this, doc); + clearCaches(this); + this.display.input.reset(); + scrollToCoords(this, doc.scrollLeft, doc.scrollTop); + this.curOp.forceScroll = true; + signalLater(this, "swapDoc", this, old); + return old + }), + + phrase: function(phraseText) { + var phrases = this.options.phrases; + return phrases && Object.prototype.hasOwnProperty.call(phrases, phraseText) ? phrases[phraseText] : phraseText + }, + + getInputField: function(){return this.display.input.getField()}, + getWrapperElement: function(){return this.display.wrapper}, + getScrollerElement: function(){return this.display.scroller}, + getGutterElement: function(){return this.display.gutters} + }; + eventMixin(CodeMirror); + + CodeMirror.registerHelper = function(type, name, value) { + if (!helpers.hasOwnProperty(type)) { helpers[type] = CodeMirror[type] = {_global: []}; } + helpers[type][name] = value; + }; + CodeMirror.registerGlobalHelper = function(type, name, predicate, value) { + CodeMirror.registerHelper(type, name, value); + helpers[type]._global.push({pred: predicate, val: value}); + }; + } + + // Used for horizontal relative motion. Dir is -1 or 1 (left or + // right), unit can be "char", "column" (like char, but doesn't + // cross line boundaries), "word" (across next word), or "group" (to + // the start of next group of word or non-word-non-whitespace + // chars). The visually param controls whether, in right-to-left + // text, direction 1 means to move towards the next index in the + // string, or towards the character to the right of the current + // position. The resulting position will have a hitSide=true + // property if it reached the end of the document. + function findPosH(doc, pos, dir, unit, visually) { + var oldPos = pos; + var origDir = dir; + var lineObj = getLine(doc, pos.line); + function findNextLine() { + var l = pos.line + dir; + if (l < doc.first || l >= doc.first + doc.size) { return false } + pos = new Pos(l, pos.ch, pos.sticky); + return lineObj = getLine(doc, l) + } + function moveOnce(boundToLine) { + var next; + if (visually) { + next = moveVisually(doc.cm, lineObj, pos, dir); + } else { + next = moveLogically(lineObj, pos, dir); + } + if (next == null) { + if (!boundToLine && findNextLine()) + { pos = endOfLine(visually, doc.cm, lineObj, pos.line, dir); } + else + { return false } + } else { + pos = next; + } + return true + } + + if (unit == "char") { + moveOnce(); + } else if (unit == "column") { + moveOnce(true); + } else if (unit == "word" || unit == "group") { + var sawType = null, group = unit == "group"; + var helper = doc.cm && doc.cm.getHelper(pos, "wordChars"); + for (var first = true;; first = false) { + if (dir < 0 && !moveOnce(!first)) { break } + var cur = lineObj.text.charAt(pos.ch) || "\n"; + var type = isWordChar(cur, helper) ? "w" + : group && cur == "\n" ? "n" + : !group || /\s/.test(cur) ? null + : "p"; + if (group && !first && !type) { type = "s"; } + if (sawType && sawType != type) { + if (dir < 0) {dir = 1; moveOnce(); pos.sticky = "after";} + break + } + + if (type) { sawType = type; } + if (dir > 0 && !moveOnce(!first)) { break } + } + } + var result = skipAtomic(doc, pos, oldPos, origDir, true); + if (equalCursorPos(oldPos, result)) { result.hitSide = true; } + return result + } + + // For relative vertical movement. Dir may be -1 or 1. Unit can be + // "page" or "line". The resulting position will have a hitSide=true + // property if it reached the end of the document. + function findPosV(cm, pos, dir, unit) { + var doc = cm.doc, x = pos.left, y; + if (unit == "page") { + var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight); + var moveAmount = Math.max(pageSize - .5 * textHeight(cm.display), 3); + y = (dir > 0 ? pos.bottom : pos.top) + dir * moveAmount; + + } else if (unit == "line") { + y = dir > 0 ? pos.bottom + 3 : pos.top - 3; + } + var target; + for (;;) { + target = coordsChar(cm, x, y); + if (!target.outside) { break } + if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break } + y += dir * 5; + } + return target + } + + // CONTENTEDITABLE INPUT STYLE + + var ContentEditableInput = function(cm) { + this.cm = cm; + this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null; + this.polling = new Delayed(); + this.composing = null; + this.gracePeriod = false; + this.readDOMTimeout = null; + }; + + ContentEditableInput.prototype.init = function (display) { + var this$1 = this; + + var input = this, cm = input.cm; + var div = input.div = display.lineDiv; + disableBrowserMagic(div, cm.options.spellcheck, cm.options.autocorrect, cm.options.autocapitalize); + + on(div, "paste", function (e) { + if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return } + // IE doesn't fire input events, so we schedule a read for the pasted content in this way + if (ie_version <= 11) { setTimeout(operation(cm, function () { return this$1.updateFromDOM(); }), 20); } + }); + + on(div, "compositionstart", function (e) { + this$1.composing = {data: e.data, done: false}; + }); + on(div, "compositionupdate", function (e) { + if (!this$1.composing) { this$1.composing = {data: e.data, done: false}; } + }); + on(div, "compositionend", function (e) { + if (this$1.composing) { + if (e.data != this$1.composing.data) { this$1.readFromDOMSoon(); } + this$1.composing.done = true; + } + }); + + on(div, "touchstart", function () { return input.forceCompositionEnd(); }); + + on(div, "input", function () { + if (!this$1.composing) { this$1.readFromDOMSoon(); } + }); + + function onCopyCut(e) { + if (signalDOMEvent(cm, e)) { return } + if (cm.somethingSelected()) { + setLastCopied({lineWise: false, text: cm.getSelections()}); + if (e.type == "cut") { cm.replaceSelection("", null, "cut"); } + } else if (!cm.options.lineWiseCopyCut) { + return + } else { + var ranges = copyableRanges(cm); + setLastCopied({lineWise: true, text: ranges.text}); + if (e.type == "cut") { + cm.operation(function () { + cm.setSelections(ranges.ranges, 0, sel_dontScroll); + cm.replaceSelection("", null, "cut"); + }); + } + } + if (e.clipboardData) { + e.clipboardData.clearData(); + var content = lastCopied.text.join("\n"); + // iOS exposes the clipboard API, but seems to discard content inserted into it + e.clipboardData.setData("Text", content); + if (e.clipboardData.getData("Text") == content) { + e.preventDefault(); + return + } + } + // Old-fashioned briefly-focus-a-textarea hack + var kludge = hiddenTextarea(), te = kludge.firstChild; + cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild); + te.value = lastCopied.text.join("\n"); + var hadFocus = document.activeElement; + selectInput(te); + setTimeout(function () { + cm.display.lineSpace.removeChild(kludge); + hadFocus.focus(); + if (hadFocus == div) { input.showPrimarySelection(); } + }, 50); + } + on(div, "copy", onCopyCut); + on(div, "cut", onCopyCut); + }; + + ContentEditableInput.prototype.prepareSelection = function () { + var result = prepareSelection(this.cm, false); + result.focus = this.cm.state.focused; + return result + }; + + ContentEditableInput.prototype.showSelection = function (info, takeFocus) { + if (!info || !this.cm.display.view.length) { return } + if (info.focus || takeFocus) { this.showPrimarySelection(); } + this.showMultipleSelections(info); + }; + + ContentEditableInput.prototype.getSelection = function () { + return this.cm.display.wrapper.ownerDocument.getSelection() + }; + + ContentEditableInput.prototype.showPrimarySelection = function () { + var sel = this.getSelection(), cm = this.cm, prim = cm.doc.sel.primary(); + var from = prim.from(), to = prim.to(); + + if (cm.display.viewTo == cm.display.viewFrom || from.line >= cm.display.viewTo || to.line < cm.display.viewFrom) { + sel.removeAllRanges(); + return + } + + var curAnchor = domToPos(cm, sel.anchorNode, sel.anchorOffset); + var curFocus = domToPos(cm, sel.focusNode, sel.focusOffset); + if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad && + cmp(minPos(curAnchor, curFocus), from) == 0 && + cmp(maxPos(curAnchor, curFocus), to) == 0) + { return } + + var view = cm.display.view; + var start = (from.line >= cm.display.viewFrom && posToDOM(cm, from)) || + {node: view[0].measure.map[2], offset: 0}; + var end = to.line < cm.display.viewTo && posToDOM(cm, to); + if (!end) { + var measure = view[view.length - 1].measure; + var map$$1 = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map; + end = {node: map$$1[map$$1.length - 1], offset: map$$1[map$$1.length - 2] - map$$1[map$$1.length - 3]}; + } + + if (!start || !end) { + sel.removeAllRanges(); + return + } + + var old = sel.rangeCount && sel.getRangeAt(0), rng; + try { rng = range(start.node, start.offset, end.offset, end.node); } + catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible + if (rng) { + if (!gecko && cm.state.focused) { + sel.collapse(start.node, start.offset); + if (!rng.collapsed) { + sel.removeAllRanges(); + sel.addRange(rng); + } + } else { + sel.removeAllRanges(); + sel.addRange(rng); + } + if (old && sel.anchorNode == null) { sel.addRange(old); } + else if (gecko) { this.startGracePeriod(); } + } + this.rememberSelection(); + }; + + ContentEditableInput.prototype.startGracePeriod = function () { + var this$1 = this; + + clearTimeout(this.gracePeriod); + this.gracePeriod = setTimeout(function () { + this$1.gracePeriod = false; + if (this$1.selectionChanged()) + { this$1.cm.operation(function () { return this$1.cm.curOp.selectionChanged = true; }); } + }, 20); + }; + + ContentEditableInput.prototype.showMultipleSelections = function (info) { + removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors); + removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection); + }; + + ContentEditableInput.prototype.rememberSelection = function () { + var sel = this.getSelection(); + this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset; + this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset; + }; + + ContentEditableInput.prototype.selectionInEditor = function () { + var sel = this.getSelection(); + if (!sel.rangeCount) { return false } + var node = sel.getRangeAt(0).commonAncestorContainer; + return contains(this.div, node) + }; + + ContentEditableInput.prototype.focus = function () { + if (this.cm.options.readOnly != "nocursor") { + if (!this.selectionInEditor()) + { this.showSelection(this.prepareSelection(), true); } + this.div.focus(); + } + }; + ContentEditableInput.prototype.blur = function () { this.div.blur(); }; + ContentEditableInput.prototype.getField = function () { return this.div }; + + ContentEditableInput.prototype.supportsTouch = function () { return true }; + + ContentEditableInput.prototype.receivedFocus = function () { + var input = this; + if (this.selectionInEditor()) + { this.pollSelection(); } + else + { runInOp(this.cm, function () { return input.cm.curOp.selectionChanged = true; }); } + + function poll() { + if (input.cm.state.focused) { + input.pollSelection(); + input.polling.set(input.cm.options.pollInterval, poll); + } + } + this.polling.set(this.cm.options.pollInterval, poll); + }; + + ContentEditableInput.prototype.selectionChanged = function () { + var sel = this.getSelection(); + return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset || + sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset + }; + + ContentEditableInput.prototype.pollSelection = function () { + if (this.readDOMTimeout != null || this.gracePeriod || !this.selectionChanged()) { return } + var sel = this.getSelection(), cm = this.cm; + // On Android Chrome (version 56, at least), backspacing into an + // uneditable block element will put the cursor in that element, + // and then, because it's not editable, hide the virtual keyboard. + // Because Android doesn't allow us to actually detect backspace + // presses in a sane way, this code checks for when that happens + // and simulates a backspace press in this case. + if (android && chrome && this.cm.options.gutters.length && isInGutter(sel.anchorNode)) { + this.cm.triggerOnKeyDown({type: "keydown", keyCode: 8, preventDefault: Math.abs}); + this.blur(); + this.focus(); + return + } + if (this.composing) { return } + this.rememberSelection(); + var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset); + var head = domToPos(cm, sel.focusNode, sel.focusOffset); + if (anchor && head) { runInOp(cm, function () { + setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll); + if (anchor.bad || head.bad) { cm.curOp.selectionChanged = true; } + }); } + }; + + ContentEditableInput.prototype.pollContent = function () { + if (this.readDOMTimeout != null) { + clearTimeout(this.readDOMTimeout); + this.readDOMTimeout = null; + } + + var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary(); + var from = sel.from(), to = sel.to(); + if (from.ch == 0 && from.line > cm.firstLine()) + { from = Pos(from.line - 1, getLine(cm.doc, from.line - 1).length); } + if (to.ch == getLine(cm.doc, to.line).text.length && to.line < cm.lastLine()) + { to = Pos(to.line + 1, 0); } + if (from.line < display.viewFrom || to.line > display.viewTo - 1) { return false } + + var fromIndex, fromLine, fromNode; + if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) { + fromLine = lineNo(display.view[0].line); + fromNode = display.view[0].node; + } else { + fromLine = lineNo(display.view[fromIndex].line); + fromNode = display.view[fromIndex - 1].node.nextSibling; + } + var toIndex = findViewIndex(cm, to.line); + var toLine, toNode; + if (toIndex == display.view.length - 1) { + toLine = display.viewTo - 1; + toNode = display.lineDiv.lastChild; + } else { + toLine = lineNo(display.view[toIndex + 1].line) - 1; + toNode = display.view[toIndex + 1].node.previousSibling; + } + + if (!fromNode) { return false } + var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine)); + var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length)); + while (newText.length > 1 && oldText.length > 1) { + if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine--; } + else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++; } + else { break } + } + + var cutFront = 0, cutEnd = 0; + var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length); + while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront)) + { ++cutFront; } + var newBot = lst(newText), oldBot = lst(oldText); + var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0), + oldBot.length - (oldText.length == 1 ? cutFront : 0)); + while (cutEnd < maxCutEnd && + newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) + { ++cutEnd; } + // Try to move start of change to start of selection if ambiguous + if (newText.length == 1 && oldText.length == 1 && fromLine == from.line) { + while (cutFront && cutFront > from.ch && + newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) { + cutFront--; + cutEnd++; + } + } + + newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd).replace(/^\u200b+/, ""); + newText[0] = newText[0].slice(cutFront).replace(/\u200b+$/, ""); + + var chFrom = Pos(fromLine, cutFront); + var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0); + if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) { + replaceRange(cm.doc, newText, chFrom, chTo, "+input"); + return true + } + }; + + ContentEditableInput.prototype.ensurePolled = function () { + this.forceCompositionEnd(); + }; + ContentEditableInput.prototype.reset = function () { + this.forceCompositionEnd(); + }; + ContentEditableInput.prototype.forceCompositionEnd = function () { + if (!this.composing) { return } + clearTimeout(this.readDOMTimeout); + this.composing = null; + this.updateFromDOM(); + this.div.blur(); + this.div.focus(); + }; + ContentEditableInput.prototype.readFromDOMSoon = function () { + var this$1 = this; + + if (this.readDOMTimeout != null) { return } + this.readDOMTimeout = setTimeout(function () { + this$1.readDOMTimeout = null; + if (this$1.composing) { + if (this$1.composing.done) { this$1.composing = null; } + else { return } + } + this$1.updateFromDOM(); + }, 80); + }; + + ContentEditableInput.prototype.updateFromDOM = function () { + var this$1 = this; + + if (this.cm.isReadOnly() || !this.pollContent()) + { runInOp(this.cm, function () { return regChange(this$1.cm); }); } + }; + + ContentEditableInput.prototype.setUneditable = function (node) { + node.contentEditable = "false"; + }; + + ContentEditableInput.prototype.onKeyPress = function (e) { + if (e.charCode == 0 || this.composing) { return } + e.preventDefault(); + if (!this.cm.isReadOnly()) + { operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0); } + }; + + ContentEditableInput.prototype.readOnlyChanged = function (val) { + this.div.contentEditable = String(val != "nocursor"); + }; + + ContentEditableInput.prototype.onContextMenu = function () {}; + ContentEditableInput.prototype.resetPosition = function () {}; + + ContentEditableInput.prototype.needsContentAttribute = true; + + function posToDOM(cm, pos) { + var view = findViewForLine(cm, pos.line); + if (!view || view.hidden) { return null } + var line = getLine(cm.doc, pos.line); + var info = mapFromLineView(view, line, pos.line); + + var order = getOrder(line, cm.doc.direction), side = "left"; + if (order) { + var partPos = getBidiPartAt(order, pos.ch); + side = partPos % 2 ? "right" : "left"; + } + var result = nodeAndOffsetInLineMap(info.map, pos.ch, side); + result.offset = result.collapse == "right" ? result.end : result.start; + return result + } + + function isInGutter(node) { + for (var scan = node; scan; scan = scan.parentNode) + { if (/CodeMirror-gutter-wrapper/.test(scan.className)) { return true } } + return false + } + + function badPos(pos, bad) { if (bad) { pos.bad = true; } return pos } + + function domTextBetween(cm, from, to, fromLine, toLine) { + var text = "", closing = false, lineSep = cm.doc.lineSeparator(), extraLinebreak = false; + function recognizeMarker(id) { return function (marker) { return marker.id == id; } } + function close() { + if (closing) { + text += lineSep; + if (extraLinebreak) { text += lineSep; } + closing = extraLinebreak = false; + } + } + function addText(str) { + if (str) { + close(); + text += str; + } + } + function walk(node) { + if (node.nodeType == 1) { + var cmText = node.getAttribute("cm-text"); + if (cmText) { + addText(cmText); + return + } + var markerID = node.getAttribute("cm-marker"), range$$1; + if (markerID) { + var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID)); + if (found.length && (range$$1 = found[0].find(0))) + { addText(getBetween(cm.doc, range$$1.from, range$$1.to).join(lineSep)); } + return + } + if (node.getAttribute("contenteditable") == "false") { return } + var isBlock = /^(pre|div|p|li|table|br)$/i.test(node.nodeName); + if (!/^br$/i.test(node.nodeName) && node.textContent.length == 0) { return } + + if (isBlock) { close(); } + for (var i = 0; i < node.childNodes.length; i++) + { walk(node.childNodes[i]); } + + if (/^(pre|p)$/i.test(node.nodeName)) { extraLinebreak = true; } + if (isBlock) { closing = true; } + } else if (node.nodeType == 3) { + addText(node.nodeValue.replace(/\u200b/g, "").replace(/\u00a0/g, " ")); + } + } + for (;;) { + walk(from); + if (from == to) { break } + from = from.nextSibling; + extraLinebreak = false; + } + return text + } + + function domToPos(cm, node, offset) { + var lineNode; + if (node == cm.display.lineDiv) { + lineNode = cm.display.lineDiv.childNodes[offset]; + if (!lineNode) { return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true) } + node = null; offset = 0; + } else { + for (lineNode = node;; lineNode = lineNode.parentNode) { + if (!lineNode || lineNode == cm.display.lineDiv) { return null } + if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) { break } + } + } + for (var i = 0; i < cm.display.view.length; i++) { + var lineView = cm.display.view[i]; + if (lineView.node == lineNode) + { return locateNodeInLineView(lineView, node, offset) } + } + } + + function locateNodeInLineView(lineView, node, offset) { + var wrapper = lineView.text.firstChild, bad = false; + if (!node || !contains(wrapper, node)) { return badPos(Pos(lineNo(lineView.line), 0), true) } + if (node == wrapper) { + bad = true; + node = wrapper.childNodes[offset]; + offset = 0; + if (!node) { + var line = lineView.rest ? lst(lineView.rest) : lineView.line; + return badPos(Pos(lineNo(line), line.text.length), bad) + } + } + + var textNode = node.nodeType == 3 ? node : null, topNode = node; + if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) { + textNode = node.firstChild; + if (offset) { offset = textNode.nodeValue.length; } + } + while (topNode.parentNode != wrapper) { topNode = topNode.parentNode; } + var measure = lineView.measure, maps = measure.maps; + + function find(textNode, topNode, offset) { + for (var i = -1; i < (maps ? maps.length : 0); i++) { + var map$$1 = i < 0 ? measure.map : maps[i]; + for (var j = 0; j < map$$1.length; j += 3) { + var curNode = map$$1[j + 2]; + if (curNode == textNode || curNode == topNode) { + var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]); + var ch = map$$1[j] + offset; + if (offset < 0 || curNode != textNode) { ch = map$$1[j + (offset ? 1 : 0)]; } + return Pos(line, ch) + } + } + } + } + var found = find(textNode, topNode, offset); + if (found) { return badPos(found, bad) } + + // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems + for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) { + found = find(after, after.firstChild, 0); + if (found) + { return badPos(Pos(found.line, found.ch - dist), bad) } + else + { dist += after.textContent.length; } + } + for (var before = topNode.previousSibling, dist$1 = offset; before; before = before.previousSibling) { + found = find(before, before.firstChild, -1); + if (found) + { return badPos(Pos(found.line, found.ch + dist$1), bad) } + else + { dist$1 += before.textContent.length; } + } + } + + // TEXTAREA INPUT STYLE + + var TextareaInput = function(cm) { + this.cm = cm; + // See input.poll and input.reset + this.prevInput = ""; + + // Flag that indicates whether we expect input to appear real soon + // now (after some event like 'keypress' or 'input') and are + // polling intensively. + this.pollingFast = false; + // Self-resetting timeout for the poller + this.polling = new Delayed(); + // Used to work around IE issue with selection being forgotten when focus moves away from textarea + this.hasSelection = false; + this.composing = null; + }; + + TextareaInput.prototype.init = function (display) { + var this$1 = this; + + var input = this, cm = this.cm; + this.createField(display); + var te = this.textarea; + + display.wrapper.insertBefore(this.wrapper, display.wrapper.firstChild); + + // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore) + if (ios) { te.style.width = "0px"; } + + on(te, "input", function () { + if (ie && ie_version >= 9 && this$1.hasSelection) { this$1.hasSelection = null; } + input.poll(); + }); + + on(te, "paste", function (e) { + if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return } + + cm.state.pasteIncoming = +new Date; + input.fastPoll(); + }); + + function prepareCopyCut(e) { + if (signalDOMEvent(cm, e)) { return } + if (cm.somethingSelected()) { + setLastCopied({lineWise: false, text: cm.getSelections()}); + } else if (!cm.options.lineWiseCopyCut) { + return + } else { + var ranges = copyableRanges(cm); + setLastCopied({lineWise: true, text: ranges.text}); + if (e.type == "cut") { + cm.setSelections(ranges.ranges, null, sel_dontScroll); + } else { + input.prevInput = ""; + te.value = ranges.text.join("\n"); + selectInput(te); + } + } + if (e.type == "cut") { cm.state.cutIncoming = +new Date; } + } + on(te, "cut", prepareCopyCut); + on(te, "copy", prepareCopyCut); + + on(display.scroller, "paste", function (e) { + if (eventInWidget(display, e) || signalDOMEvent(cm, e)) { return } + if (!te.dispatchEvent) { + cm.state.pasteIncoming = +new Date; + input.focus(); + return + } + + // Pass the `paste` event to the textarea so it's handled by its event listener. + var event = new Event("paste"); + event.clipboardData = e.clipboardData; + te.dispatchEvent(event); + }); + + // Prevent normal selection in the editor (we handle our own) + on(display.lineSpace, "selectstart", function (e) { + if (!eventInWidget(display, e)) { e_preventDefault(e); } + }); + + on(te, "compositionstart", function () { + var start = cm.getCursor("from"); + if (input.composing) { input.composing.range.clear(); } + input.composing = { + start: start, + range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-composing"}) + }; + }); + on(te, "compositionend", function () { + if (input.composing) { + input.poll(); + input.composing.range.clear(); + input.composing = null; + } + }); + }; + + TextareaInput.prototype.createField = function (_display) { + // Wraps and hides input textarea + this.wrapper = hiddenTextarea(); + // The semihidden textarea that is focused when the editor is + // focused, and receives input. + this.textarea = this.wrapper.firstChild; + }; + + TextareaInput.prototype.prepareSelection = function () { + // Redraw the selection and/or cursor + var cm = this.cm, display = cm.display, doc = cm.doc; + var result = prepareSelection(cm); + + // Move the hidden textarea near the cursor to prevent scrolling artifacts + if (cm.options.moveInputWithCursor) { + var headPos = cursorCoords(cm, doc.sel.primary().head, "div"); + var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect(); + result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, + headPos.top + lineOff.top - wrapOff.top)); + result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, + headPos.left + lineOff.left - wrapOff.left)); + } + + return result + }; + + TextareaInput.prototype.showSelection = function (drawn) { + var cm = this.cm, display = cm.display; + removeChildrenAndAdd(display.cursorDiv, drawn.cursors); + removeChildrenAndAdd(display.selectionDiv, drawn.selection); + if (drawn.teTop != null) { + this.wrapper.style.top = drawn.teTop + "px"; + this.wrapper.style.left = drawn.teLeft + "px"; + } + }; + + // Reset the input to correspond to the selection (or to be empty, + // when not typing and nothing is selected) + TextareaInput.prototype.reset = function (typing) { + if (this.contextMenuPending || this.composing) { return } + var cm = this.cm; + if (cm.somethingSelected()) { + this.prevInput = ""; + var content = cm.getSelection(); + this.textarea.value = content; + if (cm.state.focused) { selectInput(this.textarea); } + if (ie && ie_version >= 9) { this.hasSelection = content; } + } else if (!typing) { + this.prevInput = this.textarea.value = ""; + if (ie && ie_version >= 9) { this.hasSelection = null; } + } + }; + + TextareaInput.prototype.getField = function () { return this.textarea }; + + TextareaInput.prototype.supportsTouch = function () { return false }; + + TextareaInput.prototype.focus = function () { + if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.textarea)) { + try { this.textarea.focus(); } + catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM + } + }; + + TextareaInput.prototype.blur = function () { this.textarea.blur(); }; + + TextareaInput.prototype.resetPosition = function () { + this.wrapper.style.top = this.wrapper.style.left = 0; + }; + + TextareaInput.prototype.receivedFocus = function () { this.slowPoll(); }; + + // Poll for input changes, using the normal rate of polling. This + // runs as long as the editor is focused. + TextareaInput.prototype.slowPoll = function () { + var this$1 = this; + + if (this.pollingFast) { return } + this.polling.set(this.cm.options.pollInterval, function () { + this$1.poll(); + if (this$1.cm.state.focused) { this$1.slowPoll(); } + }); + }; + + // When an event has just come in that is likely to add or change + // something in the input textarea, we poll faster, to ensure that + // the change appears on the screen quickly. + TextareaInput.prototype.fastPoll = function () { + var missed = false, input = this; + input.pollingFast = true; + function p() { + var changed = input.poll(); + if (!changed && !missed) {missed = true; input.polling.set(60, p);} + else {input.pollingFast = false; input.slowPoll();} + } + input.polling.set(20, p); + }; + + // Read input from the textarea, and update the document to match. + // When something is selected, it is present in the textarea, and + // selected (unless it is huge, in which case a placeholder is + // used). When nothing is selected, the cursor sits after previously + // seen text (can be empty), which is stored in prevInput (we must + // not reset the textarea when typing, because that breaks IME). + TextareaInput.prototype.poll = function () { + var this$1 = this; + + var cm = this.cm, input = this.textarea, prevInput = this.prevInput; + // Since this is called a *lot*, try to bail out as cheaply as + // possible when it is clear that nothing happened. hasSelection + // will be the case when there is a lot of text in the textarea, + // in which case reading its value would be expensive. + if (this.contextMenuPending || !cm.state.focused || + (hasSelection(input) && !prevInput && !this.composing) || + cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq) + { return false } + + var text = input.value; + // If nothing changed, bail. + if (text == prevInput && !cm.somethingSelected()) { return false } + // Work around nonsensical selection resetting in IE9/10, and + // inexplicable appearance of private area unicode characters on + // some key combos in Mac (#2689). + if (ie && ie_version >= 9 && this.hasSelection === text || + mac && /[\uf700-\uf7ff]/.test(text)) { + cm.display.input.reset(); + return false + } + + if (cm.doc.sel == cm.display.selForContextMenu) { + var first = text.charCodeAt(0); + if (first == 0x200b && !prevInput) { prevInput = "\u200b"; } + if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo") } + } + // Find the part of the input that is actually new + var same = 0, l = Math.min(prevInput.length, text.length); + while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) { ++same; } + + runInOp(cm, function () { + applyTextInput(cm, text.slice(same), prevInput.length - same, + null, this$1.composing ? "*compose" : null); + + // Don't leave long text in the textarea, since it makes further polling slow + if (text.length > 1000 || text.indexOf("\n") > -1) { input.value = this$1.prevInput = ""; } + else { this$1.prevInput = text; } + + if (this$1.composing) { + this$1.composing.range.clear(); + this$1.composing.range = cm.markText(this$1.composing.start, cm.getCursor("to"), + {className: "CodeMirror-composing"}); + } + }); + return true + }; + + TextareaInput.prototype.ensurePolled = function () { + if (this.pollingFast && this.poll()) { this.pollingFast = false; } + }; + + TextareaInput.prototype.onKeyPress = function () { + if (ie && ie_version >= 9) { this.hasSelection = null; } + this.fastPoll(); + }; + + TextareaInput.prototype.onContextMenu = function (e) { + var input = this, cm = input.cm, display = cm.display, te = input.textarea; + if (input.contextMenuPending) { input.contextMenuPending(); } + var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop; + if (!pos || presto) { return } // Opera is difficult. + + // Reset the current text selection only if the click is done outside of the selection + // and 'resetSelectionOnContextMenu' option is true. + var reset = cm.options.resetSelectionOnContextMenu; + if (reset && cm.doc.sel.contains(pos) == -1) + { operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll); } + + var oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText; + var wrapperBox = input.wrapper.offsetParent.getBoundingClientRect(); + input.wrapper.style.cssText = "position: static"; + te.style.cssText = "position: absolute; width: 30px; height: 30px;\n top: " + (e.clientY - wrapperBox.top - 5) + "px; left: " + (e.clientX - wrapperBox.left - 5) + "px;\n z-index: 1000; background: " + (ie ? "rgba(255, 255, 255, .05)" : "transparent") + ";\n outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; + var oldScrollY; + if (webkit) { oldScrollY = window.scrollY; } // Work around Chrome issue (#2712) + display.input.focus(); + if (webkit) { window.scrollTo(null, oldScrollY); } + display.input.reset(); + // Adds "Select all" to context menu in FF + if (!cm.somethingSelected()) { te.value = input.prevInput = " "; } + input.contextMenuPending = rehide; + display.selForContextMenu = cm.doc.sel; + clearTimeout(display.detectingSelectAll); + + // Select-all will be greyed out if there's nothing to select, so + // this adds a zero-width space so that we can later check whether + // it got selected. + function prepareSelectAllHack() { + if (te.selectionStart != null) { + var selected = cm.somethingSelected(); + var extval = "\u200b" + (selected ? te.value : ""); + te.value = "\u21da"; // Used to catch context-menu undo + te.value = extval; + input.prevInput = selected ? "" : "\u200b"; + te.selectionStart = 1; te.selectionEnd = extval.length; + // Re-set this, in case some other handler touched the + // selection in the meantime. + display.selForContextMenu = cm.doc.sel; + } + } + function rehide() { + if (input.contextMenuPending != rehide) { return } + input.contextMenuPending = false; + input.wrapper.style.cssText = oldWrapperCSS; + te.style.cssText = oldCSS; + if (ie && ie_version < 9) { display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos); } + + // Try to detect the user choosing select-all + if (te.selectionStart != null) { + if (!ie || (ie && ie_version < 9)) { prepareSelectAllHack(); } + var i = 0, poll = function () { + if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 && + te.selectionEnd > 0 && input.prevInput == "\u200b") { + operation(cm, selectAll)(cm); + } else if (i++ < 10) { + display.detectingSelectAll = setTimeout(poll, 500); + } else { + display.selForContextMenu = null; + display.input.reset(); + } + }; + display.detectingSelectAll = setTimeout(poll, 200); + } + } + + if (ie && ie_version >= 9) { prepareSelectAllHack(); } + if (captureRightClick) { + e_stop(e); + var mouseup = function () { + off(window, "mouseup", mouseup); + setTimeout(rehide, 20); + }; + on(window, "mouseup", mouseup); + } else { + setTimeout(rehide, 50); + } + }; + + TextareaInput.prototype.readOnlyChanged = function (val) { + if (!val) { this.reset(); } + this.textarea.disabled = val == "nocursor"; + }; + + TextareaInput.prototype.setUneditable = function () {}; + + TextareaInput.prototype.needsContentAttribute = false; + + function fromTextArea(textarea, options) { + options = options ? copyObj(options) : {}; + options.value = textarea.value; + if (!options.tabindex && textarea.tabIndex) + { options.tabindex = textarea.tabIndex; } + if (!options.placeholder && textarea.placeholder) + { options.placeholder = textarea.placeholder; } + // Set autofocus to true if this textarea is focused, or if it has + // autofocus and no other element is focused. + if (options.autofocus == null) { + var hasFocus = activeElt(); + options.autofocus = hasFocus == textarea || + textarea.getAttribute("autofocus") != null && hasFocus == document.body; + } + + function save() {textarea.value = cm.getValue();} + + var realSubmit; + if (textarea.form) { + on(textarea.form, "submit", save); + // Deplorable hack to make the submit method do the right thing. + if (!options.leaveSubmitMethodAlone) { + var form = textarea.form; + realSubmit = form.submit; + try { + var wrappedSubmit = form.submit = function () { + save(); + form.submit = realSubmit; + form.submit(); + form.submit = wrappedSubmit; + }; + } catch(e) {} + } + } + + options.finishInit = function (cm) { + cm.save = save; + cm.getTextArea = function () { return textarea; }; + cm.toTextArea = function () { + cm.toTextArea = isNaN; // Prevent this from being ran twice + save(); + textarea.parentNode.removeChild(cm.getWrapperElement()); + textarea.style.display = ""; + if (textarea.form) { + off(textarea.form, "submit", save); + if (typeof textarea.form.submit == "function") + { textarea.form.submit = realSubmit; } + } + }; + }; + + textarea.style.display = "none"; + var cm = CodeMirror(function (node) { return textarea.parentNode.insertBefore(node, textarea.nextSibling); }, + options); + return cm + } + + function addLegacyProps(CodeMirror) { + CodeMirror.off = off; + CodeMirror.on = on; + CodeMirror.wheelEventPixels = wheelEventPixels; + CodeMirror.Doc = Doc; + CodeMirror.splitLines = splitLinesAuto; + CodeMirror.countColumn = countColumn; + CodeMirror.findColumn = findColumn; + CodeMirror.isWordChar = isWordCharBasic; + CodeMirror.Pass = Pass; + CodeMirror.signal = signal; + CodeMirror.Line = Line; + CodeMirror.changeEnd = changeEnd; + CodeMirror.scrollbarModel = scrollbarModel; + CodeMirror.Pos = Pos; + CodeMirror.cmpPos = cmp; + CodeMirror.modes = modes; + CodeMirror.mimeModes = mimeModes; + CodeMirror.resolveMode = resolveMode; + CodeMirror.getMode = getMode; + CodeMirror.modeExtensions = modeExtensions; + CodeMirror.extendMode = extendMode; + CodeMirror.copyState = copyState; + CodeMirror.startState = startState; + CodeMirror.innerMode = innerMode; + CodeMirror.commands = commands; + CodeMirror.keyMap = keyMap; + CodeMirror.keyName = keyName; + CodeMirror.isModifierKey = isModifierKey; + CodeMirror.lookupKey = lookupKey; + CodeMirror.normalizeKeyMap = normalizeKeyMap; + CodeMirror.StringStream = StringStream; + CodeMirror.SharedTextMarker = SharedTextMarker; + CodeMirror.TextMarker = TextMarker; + CodeMirror.LineWidget = LineWidget; + CodeMirror.e_preventDefault = e_preventDefault; + CodeMirror.e_stopPropagation = e_stopPropagation; + CodeMirror.e_stop = e_stop; + CodeMirror.addClass = addClass; + CodeMirror.contains = contains; + CodeMirror.rmClass = rmClass; + CodeMirror.keyNames = keyNames; + } + + // EDITOR CONSTRUCTOR + + defineOptions(CodeMirror); + + addEditorMethods(CodeMirror); + + // Set up methods on CodeMirror's prototype to redirect to the editor's document. + var dontDelegate = "iter insert remove copy getEditor constructor".split(" "); + for (var prop in Doc.prototype) { if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0) + { CodeMirror.prototype[prop] = (function(method) { + return function() {return method.apply(this.doc, arguments)} + })(Doc.prototype[prop]); } } + + eventMixin(Doc); + CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput}; + + // Extra arguments are stored as the mode's dependencies, which is + // used by (legacy) mechanisms like loadmode.js to automatically + // load a mode. (Preferred mechanism is the require/define calls.) + CodeMirror.defineMode = function(name/*, mode, …*/) { + if (!CodeMirror.defaults.mode && name != "null") { CodeMirror.defaults.mode = name; } + defineMode.apply(this, arguments); + }; + + CodeMirror.defineMIME = defineMIME; + + // Minimal default mode. + CodeMirror.defineMode("null", function () { return ({token: function (stream) { return stream.skipToEnd(); }}); }); + CodeMirror.defineMIME("text/plain", "null"); + + // EXTENSIONS + + CodeMirror.defineExtension = function (name, func) { + CodeMirror.prototype[name] = func; + }; + CodeMirror.defineDocExtension = function (name, func) { + Doc.prototype[name] = func; + }; + + CodeMirror.fromTextArea = fromTextArea; + + addLegacyProps(CodeMirror); + + CodeMirror.version = "5.45.0"; + + return CodeMirror; + +}))); + + +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +;(function(mod) { + if (typeof exports == "object" && typeof module == "object") + // CommonJS + mod(require("../../lib/codemirror")) + else if (typeof define == "function" && define.amd) + // AMD + define(["../../lib/codemirror"], mod) + // Plain browser env + else mod(CodeMirror) +})(function(CodeMirror) { + "use strict" + + var HINT_ELEMENT_CLASS = "CodeMirror-hint" + var ACTIVE_HINT_ELEMENT_CLASS = "CodeMirror-hint-active" + + // This is the old interface, kept around for now to stay + // backwards-compatible. + CodeMirror.showHint = function(cm, getHints, options) { + if (!getHints) return cm.showHint(options) + if (options && options.async) getHints.async = true + var newOpts = { hint: getHints } + if (options) for (var prop in options) newOpts[prop] = options[prop] + return cm.showHint(newOpts) + } + + CodeMirror.defineExtension("showHint", function(options) { + options = parseOptions(this, this.getCursor("start"), options) + var selections = this.listSelections() + if (selections.length > 1) return + // By default, don't allow completion when something is selected. + // A hint function can have a `supportsSelection` property to + // indicate that it can handle selections. + if (this.somethingSelected()) { + if (!options.hint.supportsSelection) return + // Don't try with cross-line selections + for (var i = 0; i < selections.length; i++) if (selections[i].head.line != selections[i].anchor.line) return + } + + if (this.state.completionActive) this.state.completionActive.close() + var completion = (this.state.completionActive = new Completion(this, options)) + if (!completion.options.hint) return + + CodeMirror.signal(this, "startCompletion", this) + completion.update(true) + }) + + CodeMirror.defineExtension("closeHint", function() { + if (this.state.completionActive) this.state.completionActive.close() + }) + + function Completion(cm, options) { + this.cm = cm + this.options = options + this.widget = null + this.debounce = 0 + this.tick = 0 + this.startPos = this.cm.getCursor("start") + this.startLen = this.cm.getLine(this.startPos.line).length - this.cm.getSelection().length + + var self = this + cm.on( + "cursorActivity", + (this.activityFunc = function() { + self.cursorActivity() + }) + ) + } + + var requestAnimationFrame = + window.requestAnimationFrame || + function(fn) { + return setTimeout(fn, 1000 / 60) + } + var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout + + Completion.prototype = { + close: function() { + if (!this.active()) return + this.cm.state.completionActive = null + this.tick = null + this.cm.off("cursorActivity", this.activityFunc) + + if (this.widget && this.data) CodeMirror.signal(this.data, "close") + if (this.widget) this.widget.close() + CodeMirror.signal(this.cm, "endCompletion", this.cm) + }, + + active: function() { + return this.cm.state.completionActive == this + }, + + pick: function(data, i) { + var completion = data.list[i] + if (completion.hint) completion.hint(this.cm, data, completion) + else this.cm.replaceRange(getText(completion), completion.from || data.from, completion.to || data.to, "complete") + CodeMirror.signal(data, "pick", completion) + this.close() + }, + + cursorActivity: function() { + if (this.debounce) { + cancelAnimationFrame(this.debounce) + this.debounce = 0 + } + + var pos = this.cm.getCursor(), + line = this.cm.getLine(pos.line) + if ( + pos.line != this.startPos.line || + line.length - pos.ch != this.startLen - this.startPos.ch || + pos.ch < this.startPos.ch || + this.cm.somethingSelected() || + (!pos.ch || this.options.closeCharacters.test(line.charAt(pos.ch - 1))) + ) { + this.close() + } else { + var self = this + this.debounce = requestAnimationFrame(function() { + self.update() + }) + if (this.widget) this.widget.disable() + } + }, + + update: function(first) { + if (this.tick == null) return + var self = this, + myTick = ++this.tick + fetchHints(this.options.hint, this.cm, this.options, function(data) { + if (self.tick == myTick) self.finishUpdate(data, first) + }) + }, + + finishUpdate: function(data, first) { + if (this.data) CodeMirror.signal(this.data, "update") + + var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle) + if (this.widget) this.widget.close() + + this.data = data + + if (data && data.list.length) { + if (picked && data.list.length == 1) { + this.pick(data, 0) + } else { + this.widget = new Widget(this, data) + CodeMirror.signal(data, "shown") + } + } + } + } + + function parseOptions(cm, pos, options) { + var editor = cm.options.hintOptions + var out = {} + for (var prop in defaultOptions) out[prop] = defaultOptions[prop] + if (editor) for (var prop in editor) if (editor[prop] !== undefined) out[prop] = editor[prop] + if (options) for (var prop in options) if (options[prop] !== undefined) out[prop] = options[prop] + if (out.hint.resolve) out.hint = out.hint.resolve(cm, pos) + return out + } + + function getText(completion) { + if (typeof completion == "string") return completion + else return completion.text + } + + function buildKeyMap(completion, handle) { + var baseMap = { + Up: function() { + handle.moveFocus(-1) + }, + Down: function() { + handle.moveFocus(1) + }, + PageUp: function() { + handle.moveFocus(-handle.menuSize() + 1, true) + }, + PageDown: function() { + handle.moveFocus(handle.menuSize() - 1, true) + }, + Home: function() { + handle.setFocus(0) + }, + End: function() { + handle.setFocus(handle.length - 1) + }, + Enter: handle.pick, + Tab: handle.pick, + Esc: handle.close + } + + var mac = /Mac/.test(navigator.platform) + + if (mac) { + baseMap["Ctrl-P"] = function() { + handle.moveFocus(-1) + } + baseMap["Ctrl-N"] = function() { + handle.moveFocus(1) + } + } + + var custom = completion.options.customKeys + var ourMap = custom ? {} : baseMap + function addBinding(key, val) { + var bound + if (typeof val != "string") + bound = function(cm) { + return val(cm, handle) + } + // This mechanism is deprecated + else if (baseMap.hasOwnProperty(val)) bound = baseMap[val] + else bound = val + ourMap[key] = bound + } + if (custom) for (var key in custom) if (custom.hasOwnProperty(key)) addBinding(key, custom[key]) + var extra = completion.options.extraKeys + if (extra) for (var key in extra) if (extra.hasOwnProperty(key)) addBinding(key, extra[key]) + return ourMap + } + + function getHintElement(hintsElement, el) { + while (el && el != hintsElement) { + if (el.nodeName.toUpperCase() === "LI" && el.parentNode == hintsElement) return el + el = el.parentNode + } + } + + function Widget(completion, data) { + this.completion = completion + this.data = data + this.picked = false + var widget = this, + cm = completion.cm + var ownerDocument = cm.getInputField().ownerDocument + var parentWindow = ownerDocument.defaultView || ownerDocument.parentWindow + + var hints = (this.hints = ownerDocument.createElement("ul")) + var theme = completion.cm.options.theme + hints.className = "CodeMirror-hints " + theme + this.selectedHint = data.selectedHint || 0 + + var completions = data.list + for (var i = 0; i < completions.length; ++i) { + var elt = hints.appendChild(ownerDocument.createElement("li")), + cur = completions[i] + var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? "" : " " + ACTIVE_HINT_ELEMENT_CLASS) + if (cur.className != null) className = cur.className + " " + className + elt.className = className + if (cur.render) cur.render(elt, data, cur) + else elt.appendChild(ownerDocument.createTextNode(cur.displayText || getText(cur))) + elt.hintId = i + } + + var pos = cm.cursorCoords(completion.options.alignWithWord ? data.from : null) + var left = pos.left, + top = pos.bottom, + below = true + hints.style.left = left + "px" + hints.style.top = top + "px" + // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor. + var winW = + parentWindow.innerWidth || Math.max(ownerDocument.body.offsetWidth, ownerDocument.documentElement.offsetWidth) + var winH = + parentWindow.innerHeight || Math.max(ownerDocument.body.offsetHeight, ownerDocument.documentElement.offsetHeight) + ;(completion.options.container || ownerDocument.body).appendChild(hints) + var box = hints.getBoundingClientRect(), + overlapY = box.bottom - winH + var scrolls = hints.scrollHeight > hints.clientHeight + 1 + var startScroll = cm.getScrollInfo() + + if (overlapY > 0) { + var height = box.bottom - box.top, + curTop = pos.top - (pos.bottom - box.top) + if (curTop - height > 0) { + // Fits above cursor + hints.style.top = (top = pos.top - height) + "px" + below = false + } else if (height > winH) { + hints.style.height = winH - 5 + "px" + hints.style.top = (top = pos.bottom - box.top) + "px" + var cursor = cm.getCursor() + if (data.from.ch != cursor.ch) { + pos = cm.cursorCoords(cursor) + hints.style.left = (left = pos.left) + "px" + box = hints.getBoundingClientRect() + } + } + } + var overlapX = box.right - winW + if (overlapX > 0) { + if (box.right - box.left > winW) { + hints.style.width = winW - 5 + "px" + overlapX -= box.right - box.left - winW + } + hints.style.left = (left = pos.left - overlapX) + "px" + } + if (scrolls) + for (var node = hints.firstChild; node; node = node.nextSibling) + node.style.paddingRight = cm.display.nativeBarWidth + "px" + + cm.addKeyMap( + (this.keyMap = buildKeyMap(completion, { + moveFocus: function(n, avoidWrap) { + widget.changeActive(widget.selectedHint + n, avoidWrap) + }, + setFocus: function(n) { + widget.changeActive(n) + }, + menuSize: function() { + return widget.screenAmount() + }, + length: completions.length, + close: function() { + completion.close() + }, + pick: function() { + widget.pick() + }, + data: data + })) + ) + + if (completion.options.closeOnUnfocus) { + var closingOnBlur + cm.on( + "blur", + (this.onBlur = function() { + closingOnBlur = setTimeout(function() { + completion.close() + }, 100) + }) + ) + cm.on( + "focus", + (this.onFocus = function() { + clearTimeout(closingOnBlur) + }) + ) + } + + cm.on( + "scroll", + (this.onScroll = function() { + var curScroll = cm.getScrollInfo(), + editor = cm.getWrapperElement().getBoundingClientRect() + var newTop = top + startScroll.top - curScroll.top + var point = + newTop - (parentWindow.pageYOffset || (ownerDocument.documentElement || ownerDocument.body).scrollTop) + if (!below) point += hints.offsetHeight + if (point <= editor.top || point >= editor.bottom) return completion.close() + hints.style.top = newTop + "px" + hints.style.left = left + startScroll.left - curScroll.left + "px" + }) + ) + + CodeMirror.on(hints, "dblclick", function(e) { + var t = getHintElement(hints, e.target || e.srcElement) + if (t && t.hintId != null) { + widget.changeActive(t.hintId) + widget.pick() + } + }) + + CodeMirror.on(hints, "click", function(e) { + var t = getHintElement(hints, e.target || e.srcElement) + if (t && t.hintId != null) { + widget.changeActive(t.hintId) + if (completion.options.completeOnSingleClick) widget.pick() + } + }) + + CodeMirror.on(hints, "mousedown", function() { + setTimeout(function() { + cm.focus() + }, 20) + }) + + CodeMirror.signal(data, "select", completions[this.selectedHint], hints.childNodes[this.selectedHint]) + return true + } + + Widget.prototype = { + close: function() { + if (this.completion.widget != this) return + this.completion.widget = null + this.hints.parentNode.removeChild(this.hints) + this.completion.cm.removeKeyMap(this.keyMap) + + var cm = this.completion.cm + if (this.completion.options.closeOnUnfocus) { + cm.off("blur", this.onBlur) + cm.off("focus", this.onFocus) + } + cm.off("scroll", this.onScroll) + }, + + disable: function() { + this.completion.cm.removeKeyMap(this.keyMap) + var widget = this + this.keyMap = { + Enter: function() { + widget.picked = true + } + } + this.completion.cm.addKeyMap(this.keyMap) + }, + + pick: function() { + this.completion.pick(this.data, this.selectedHint) + }, + + changeActive: function(i, avoidWrap) { + if (i >= this.data.list.length) i = avoidWrap ? this.data.list.length - 1 : 0 + else if (i < 0) i = avoidWrap ? 0 : this.data.list.length - 1 + if (this.selectedHint == i) return + var node = this.hints.childNodes[this.selectedHint] + if (node) node.className = node.className.replace(" " + ACTIVE_HINT_ELEMENT_CLASS, "") + node = this.hints.childNodes[(this.selectedHint = i)] + node.className += " " + ACTIVE_HINT_ELEMENT_CLASS + if (node.offsetTop < this.hints.scrollTop) this.hints.scrollTop = node.offsetTop - 3 + else if (node.offsetTop + node.offsetHeight > this.hints.scrollTop + this.hints.clientHeight) + this.hints.scrollTop = node.offsetTop + node.offsetHeight - this.hints.clientHeight + 3 + CodeMirror.signal(this.data, "select", this.data.list[this.selectedHint], node) + }, + + screenAmount: function() { + return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1 + } + } + + function applicableHelpers(cm, helpers) { + if (!cm.somethingSelected()) return helpers + var result = [] + for (var i = 0; i < helpers.length; i++) if (helpers[i].supportsSelection) result.push(helpers[i]) + return result + } + + function fetchHints(hint, cm, options, callback) { + if (hint.async) { + hint(cm, callback, options) + } else { + var result = hint(cm, options) + if (result && result.then) result.then(callback) + else callback(result) + } + } + + function resolveAutoHints(cm, pos) { + var helpers = cm.getHelpers(pos, "hint"), + words + if (helpers.length) { + var resolved = function(cm, callback, options) { + var app = applicableHelpers(cm, helpers) + function run(i) { + if (i == app.length) return callback(null) + fetchHints(app[i], cm, options, function(result) { + if (result && result.list.length > 0) callback(result) + else run(i + 1) + }) + } + run(0) + } + resolved.async = true + resolved.supportsSelection = true + return resolved + } else if ((words = cm.getHelper(cm.getCursor(), "hintWords"))) { + return function(cm) { + return CodeMirror.hint.fromList(cm, { words: words }) + } + } else if (CodeMirror.hint.anyword) { + return function(cm, options) { + return CodeMirror.hint.anyword(cm, options) + } + } else { + return function() {} + } + } + + CodeMirror.registerHelper("hint", "auto", { + resolve: resolveAutoHints + }) + + CodeMirror.registerHelper("hint", "fromList", function(cm, options) { + var cur = cm.getCursor(), + token = cm.getTokenAt(cur) + var term, + from = CodeMirror.Pos(cur.line, token.start), + to = cur + if (token.start < cur.ch && /\w/.test(token.string.charAt(cur.ch - token.start - 1))) { + term = token.string.substr(0, cur.ch - token.start) + } else { + term = "" + from = cur + } + var found = [] + for (var i = 0; i < options.words.length; i++) { + var word = options.words[i] + if (word.slice(0, term.length) == term) found.push(word) + } + + if (found.length) return { list: found, from: from, to: to } + }) + + CodeMirror.commands.autocomplete = CodeMirror.showHint + + var defaultOptions = { + hint: CodeMirror.hint.auto, + completeSingle: true, + alignWithWord: true, + closeCharacters: /[\s()\[\]{};:>,]/, + closeOnUnfocus: true, + completeOnSingleClick: true, + container: null, + customKeys: null, + extraKeys: null + } + + CodeMirror.defineOption("hintOptions", null) +}) + + +class Timer { + constructor() { + this._tickTime = Date.now() - (Utils.isNodeJs() ? 1000 * process.uptime() : 0) + this._firstTickTime = this._tickTime + } + tick(msg) { + const elapsed = Date.now() - this._tickTime + if (msg) console.log(`${elapsed}ms ${msg}`) + this._tickTime = Date.now() + return elapsed + } + getTotalElapsedTime() { + return Date.now() - this._firstTickTime + } +} +class Utils { + static getFileExtension(filepath = "") { + const match = filepath.match(/\.([^\.]+)$/) + return (match && match[1]) || "" + } + static ensureFolderEndsInSlash(folder) { + return folder.replace(/\/$/, "") + "/" + } + static runCommand(instance, command = "", param = undefined) { + const run = name => { + console.log(`Running ${name}:`) + instance[name](param) + } + if (instance[command + "Command"]) return run(command + "Command") + // Get commands from both the child and parent classes + const classes = [Object.getPrototypeOf(instance), Object.getPrototypeOf(Object.getPrototypeOf(instance))] + const allCommands = classes.map(classInstance => Object.getOwnPropertyNames(classInstance).filter(word => word.endsWith("Command"))).flat() + allCommands.sort() + const commandAsNumber = parseInt(command) - 1 + if (command.match(/^\d+$/) && allCommands[commandAsNumber]) return run(allCommands[commandAsNumber]) + console.log(`\n❌ No command provided. Available commands:\n\n` + allCommands.map((name, index) => `${index + 1}. ${name.replace("Command", "")}`).join("\n") + "\n") + } + static removeReturnChars(str = "") { + return str.replace(/\r/g, "") + } + static isAbsoluteUrl(url) { + return url.startsWith("https://") || url.startsWith("http://") + } + static removeEmptyLines(str = "") { + return str.replace(/\n\n+/g, "\n") + } + static shiftRight(str = "", numSpaces = 1) { + let spaces = " ".repeat(numSpaces) + return str.replace(/\n/g, `\n${spaces}`) + } + static getLinks(str = "") { + const _re = new RegExp("(^|[ \t\r\n])((ftp|http|https):(([A-Za-z0-9$_.+!*(),;/?:@&~=-])|%[A-Fa-f0-9]{2}){2,}(#([a-zA-Z0-9][a-zA-Z0-9$_.+!*(),;/?:@&~=%-]*))?([A-Za-z0-9$_+!*();/?:~-]))", "g") + return str.match(_re) || [] + } + // Only allow text content and inline styling. Don't allow HTML tags or any nested scroll tags or escape characters. + static escapeScrollAndHtml(content = "") { + return content.replace(/ word.includes(delimiter)) + if (hit) throw `Delimiter "${delimiter}" found in hit` + } + // https://github.com/rigoneri/indefinite-article.js/blob/master/indefinite-article.js + static getIndefiniteArticle(phrase) { + // Getting the first word + const match = /\w+/.exec(phrase) + let word + if (match) word = match[0] + else return "an" + var l_word = word.toLowerCase() + // Specific start of words that should be preceded by 'an' + var alt_cases = ["honest", "hour", "hono"] + for (var i in alt_cases) { + if (l_word.indexOf(alt_cases[i]) == 0) return "an" + } + // Single letter word which should be preceded by 'an' + if (l_word.length == 1) { + if ("aedhilmnorsx".indexOf(l_word) >= 0) return "an" + else return "a" + } + // Capital words which should likely be preceded by 'an' + if (word.match(/(?!FJO|[HLMNS]Y.|RY[EO]|SQU|(F[LR]?|[HL]|MN?|N|RH?|S[CHKLMNPTVW]?|X(YL)?)[AEIOU])[FHLMNRSX][A-Z]/)) { + return "an" + } + // Special cases where a word that begins with a vowel should be preceded by 'a' + const regexes = [/^e[uw]/, /^onc?e\b/, /^uni([^nmd]|mo)/, /^u[bcfhjkqrst][aeiou]/] + for (var i in regexes) { + if (l_word.match(regexes[i])) return "a" + } + // Special capital words (UK, UN) + if (word.match(/^U[NK][AIEO]/)) { + return "a" + } else if (word == word.toUpperCase()) { + if ("aedhilmnorsx".indexOf(l_word[0]) >= 0) return "an" + else return "a" + } + // Basic method of words that begin with a vowel being preceded by 'an' + if ("aeiou".indexOf(l_word[0]) >= 0) return "an" + // Instances where y follwed by specific letters is preceded by 'an' + if (l_word.match(/^y(b[lor]|cl[ea]|fere|gg|p[ios]|rou|tt)/)) return "an" + return "a" + } + static htmlEscaped(content = "") { + return content.replace(/()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/) + } + static capitalizeFirstLetter(str) { + return str.charAt(0).toUpperCase() + str.slice(1) + } + // generate a random alpha numeric hash: + static getRandomCharacters(length) { + const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + let result = "" + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * characters.length)) + } + return result + } + static isNodeJs() { + return typeof exports !== "undefined" + } + static findProjectRoot(startingDirName, projectName) { + const fs = require("fs") + const getProjectName = dirName => { + if (!dirName) throw new Error(`dirName undefined when attempting to findProjectRoot for project "${projectName}" starting in "${startingDirName}"`) + const parts = dirName.split("/") + const filename = parts.join("/") + "/" + "package.json" + if (fs.existsSync(filename) && JSON.parse(fs.readFileSync(filename, "utf8")).name === projectName) return parts.join("/") + "/" + parts.pop() + return parts + } + let result = getProjectName(startingDirName) + while (typeof result !== "string" && result.length > 0) { + result = getProjectName(result.join("/")) + } + if (result.length === 0) throw new Error(`Project root "${projectName}" in folder ${startingDirName} not found.`) + return result + } + static titleToPermalink(str) { + return str + .replace(/[\/\_\:\\\[\]]/g, "-") + .replace(/π/g, "pi") + .replace(/`/g, "tick") + .replace(/\$/g, "dollar-sign") + .replace(/\*$/g, "-star") + .replace(/^\*/g, "star-") + .replace(/\*/g, "-star-") + .replace(/\'+$/g, "q") + .replace(/^@/g, "at-") + .replace(/@$/g, "-at") + .replace(/@/g, "-at-") + .replace(/[\'\"\,\ū]/g, "") + .replace(/^\#/g, "sharp-") + .replace(/\#$/g, "-sharp") + .replace(/\#/g, "-sharp-") + .replace(/[\(\)]/g, "") + .replace(/\+\+$/g, "pp") + .replace(/\+$/g, "p") + .replace(/^\!/g, "bang-") + .replace(/\!$/g, "-bang") + .replace(/\!/g, "-bang-") + .replace(/\&/g, "-n-") + .replace(/[\+ ]/g, "-") + .replace(/[^a-zA-Z0-9\-\.]/g, "") + .toLowerCase() + } + static escapeRegExp(str) { + return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + } + static sum(arr) { + return arr.reduce((curr, next) => curr + next, 0) + } + static makeVector(length, fill = 0) { + return new Array(length).fill(fill) + } + static makeMatrix(cols, rows, fill = 0) { + const matrix = [] + while (rows) { + matrix.push(Utils.makeVector(cols, fill)) + rows-- + } + return matrix + } + static removeNonAscii(str) { + // https://stackoverflow.com/questions/20856197/remove-non-ascii-character-in-string + return str.replace(/[^\x00-\x7F]/g, "") + } + static getMethodFromDotPath(context, str) { + const methodParts = str.split(".") + while (methodParts.length > 1) { + const methodName = methodParts.shift() + if (!context[methodName]) throw new Error(`${methodName} is not a method on ${context}`) + context = context[methodName]() + } + const final = methodParts.shift() + return [context, final] + } + static requireAbsOrRelative(filePath, contextFilePath) { + if (!filePath.startsWith(".")) return require(filePath) + const path = require("path") + const folder = this.getPathWithoutFileName(contextFilePath) + const file = path.resolve(folder + "/" + filePath) + return require(file) + } + // Removes last ".*" from this string + static removeFileExtension(filename) { + return filename ? filename.replace(/\.[^\.]+$/, "") : "" + } + static getFileName(path) { + const normalizedPath = path.replace(/\\/g, "/") + const parts = normalizedPath.split("/") + return parts.pop() + } + static getPathWithoutFileName(path) { + const normalizedPath = path.replace(/\\/g, "/") + const parts = normalizedPath.split("/") + parts.pop() + return parts.join("/") + } + static shuffleInPlace(arr, seed = Date.now()) { + // https://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array + const randFn = Utils._getPseudoRandom0to1FloatGenerator(seed) + for (let index = arr.length - 1; index > 0; index--) { + const tempIndex = Math.floor(randFn() * (index + 1)) + ;[arr[index], arr[tempIndex]] = [arr[tempIndex], arr[index]] + } + return arr + } + // Only allows a-zA-Z0-9-_ (And optionally .) + static _permalink(str, reg) { + return str.length ? str.toLowerCase().replace(reg, "").replace(/ /g, "-") : "" + } + static isValueEmpty(value) { + return value === undefined || value === "" || (typeof value === "number" && isNaN(value)) || (value instanceof Date && isNaN(value)) + } + static stringToPermalink(str) { + return this._permalink(str, /[^a-z0-9- _\.]/gi) + } + static getAvailablePermalink(permalink, doesFileExistSyncFn) { + const extension = this.getFileExtension(permalink) + permalink = this.removeFileExtension(permalink) + const originalPermalink = permalink + let num = 2 + let suffix = "" + let filename = `${originalPermalink}${suffix}.${extension}` + while (doesFileExistSyncFn(filename)) { + filename = `${originalPermalink}${suffix}.${extension}` + suffix = "-" + num + num++ + } + return filename + } + static getNextOrPrevious(arr, item) { + const length = arr.length + const index = arr.indexOf(item) + if (length === 1) return undefined + if (index === length - 1) return arr[index - 1] + return arr[index + 1] + } + static toggle(currentValue, values) { + const index = values.indexOf(currentValue) + return index === -1 || index + 1 === values.length ? values[0] : values[index + 1] + } + static getClassNameFromFilePath(filepath) { + return this.removeFileExtension(this.getFileName(filepath)) + } + static joinArraysOn(joinOn, arrays, columns) { + const rows = {} + let index = 0 + if (!columns) columns = arrays.map(arr => Object.keys(arr[0])) + arrays.forEach((arr, index) => { + const cols = columns[index] + arr.forEach(row => { + const key = joinOn ? row[joinOn] : index++ + if (!rows[key]) rows[key] = {} + const obj = rows[key] + cols.forEach(col => (obj[col] = row[col])) + }) + }) + return Object.values(rows) + } + static getParentFolder(path) { + if (path.endsWith("/")) path = this._removeLastSlash(path) + return path.replace(/\/[^\/]*$/, "") + "/" + } + static _removeLastSlash(path) { + return path.replace(/\/$/, "") + } + static _listToEnglishText(list, limit = 5) { + const len = list.length + if (!len) return "" + if (len === 1) return `'${list[0]}'` + const clone = list.slice(0, limit).map(item => `'${item}'`) + const last = clone.pop() + if (len <= limit) return clone.join(", ") + ` and ${last}` + return clone.join(", ") + ` and ${len - limit} more` + } + // todo: refactor so instead of str input takes an array of cells(strings) and scans each indepndently. + static _chooseDelimiter(str) { + const del = " ,|\t;^%$!#@~*&+-=_:?.{}[]()<>/".split("").find(idea => !str.includes(idea)) + if (!del) throw new Error("Could not find a delimiter") + return del + } + static flatten(arr) { + if (arr.flat) return arr.flat() + return arr.reduce((acc, val) => acc.concat(val), []) + } + static escapeBackTicks(str) { + return str.replace(/\`/g, "\\`").replace(/\$\{/g, "\\${") + } + static ucfirst(str) { + return str.charAt(0).toUpperCase() + str.slice(1) + } + // Adapted from: https://github.com/dcporter/didyoumean.js/blob/master/didYouMean-1.2.1.js + static didYouMean(str = "", options = [], caseSensitive = false, threshold = 0.4, thresholdAbsolute = 20) { + if (!caseSensitive) str = str.toLowerCase() + // Calculate the initial value (the threshold) if present. + const thresholdRelative = threshold * str.length + let maximumEditDistanceToBeBestMatch + if (thresholdRelative !== null && thresholdAbsolute !== null) maximumEditDistanceToBeBestMatch = Math.min(thresholdRelative, thresholdAbsolute) + else if (thresholdRelative !== null) maximumEditDistanceToBeBestMatch = thresholdRelative + else if (thresholdAbsolute !== null) maximumEditDistanceToBeBestMatch = thresholdAbsolute + // Get the edit distance to each option. If the closest one is less than 40% (by default) of str's length, then return it. + let closestMatch + const len = options.length + for (let optionIndex = 0; optionIndex < len; optionIndex++) { + const candidate = options[optionIndex] + if (!candidate) continue + const editDistance = Utils._getEditDistance(str, caseSensitive ? candidate : candidate.toLowerCase(), maximumEditDistanceToBeBestMatch) + if (editDistance < maximumEditDistanceToBeBestMatch) { + maximumEditDistanceToBeBestMatch = editDistance + closestMatch = candidate + } + } + return closestMatch + } + // Adapted from: https://github.com/dcporter/didyoumean.js/blob/master/didYouMean-1.2.1.js + static _getEditDistance(stringA, stringB, maxInt) { + // Handle null or undefined max. + maxInt = maxInt || maxInt === 0 ? maxInt : Utils.MAX_INT + const aLength = stringA.length + const bLength = stringB.length + // Fast path - no A or B. + if (aLength === 0) return Math.min(maxInt + 1, bLength) + if (bLength === 0) return Math.min(maxInt + 1, aLength) + // Fast path - length diff larger than max. + if (Math.abs(aLength - bLength) > maxInt) return maxInt + 1 + // Slow path. + const matrix = [] + // Set up the first row ([0, 1, 2, 3, etc]). + for (let bIndex = 0; bIndex <= bLength; bIndex++) { + matrix[bIndex] = [bIndex] + } + // Set up the first column (same). + for (let aIndex = 0; aIndex <= aLength; aIndex++) { + matrix[0][aIndex] = aIndex + } + let colMin + let minJ + let maxJ + // Loop over the rest of the columns. + for (let bIndex = 1; bIndex <= bLength; bIndex++) { + colMin = Utils.MAX_INT + minJ = 1 + if (bIndex > maxInt) minJ = bIndex - maxInt + maxJ = bLength + 1 + if (maxJ > maxInt + bIndex) maxJ = maxInt + bIndex + // Loop over the rest of the rows. + for (let aIndex = 1; aIndex <= aLength; aIndex++) { + // If j is out of bounds, just put a large value in the slot. + if (aIndex < minJ || aIndex > maxJ) matrix[bIndex][aIndex] = maxInt + 1 + // Otherwise do the normal Levenshtein thing. + else { + // If the characters are the same, there's no change in edit distance. + if (stringB.charAt(bIndex - 1) === stringA.charAt(aIndex - 1)) matrix[bIndex][aIndex] = matrix[bIndex - 1][aIndex - 1] + // Otherwise, see if we're substituting, inserting or deleting. + else + matrix[bIndex][aIndex] = Math.min( + matrix[bIndex - 1][aIndex - 1] + 1, // Substitute + Math.min( + matrix[bIndex][aIndex - 1] + 1, // Insert + matrix[bIndex - 1][aIndex] + 1 + ) + ) // Delete + } + // Either way, update colMin. + if (matrix[bIndex][aIndex] < colMin) colMin = matrix[bIndex][aIndex] + } + // If this column's minimum is greater than the allowed maximum, there's no point + // in going on with life. + if (colMin > maxInt) return maxInt + 1 + } + // If we made it this far without running into the max, then return the final matrix value. + return matrix[bLength][aLength] + } + static getLineIndexAtCharacterPosition(str, index) { + const lines = str.split("\n") + const len = lines.length + let position = 0 + for (let lineNumber = 0; lineNumber < len; lineNumber++) { + position += lines[lineNumber].length + if (position >= index) return lineNumber + } + } + static resolvePath(filePath, programFilepath) { + // For use in Node.js only + if (!filePath.startsWith(".")) return filePath + const path = require("path") + const folder = this.getPathWithoutFileName(programFilepath) + return path.resolve(folder + "/" + filePath) + } + static resolveProperty(obj, path, separator = ".") { + const properties = Array.isArray(path) ? path : path.split(separator) + return properties.reduce((prev, curr) => prev && prev[curr], obj) + } + static appendCodeAndReturnValueOnWindow(code, name) { + const script = document.createElement("script") + script.innerHTML = code + document.head.appendChild(script) + return window[name] + } + static formatStr(str, catchAllCellDelimiter = " ", parameterMap) { + return str.replace(/{([^\}]+)}/g, (match, path) => { + const val = parameterMap[path] + if (!val) return "" + return Array.isArray(val) ? val.join(catchAllCellDelimiter) : val + }) + } + static stripHtml(text) { + return text && text.replace ? text.replace(/<(?:.|\n)*?>/gm, "") : text + } + static getUniqueWordsArray(allWords) { + const words = allWords.replace(/\n/g, " ").split(" ") + const index = {} + words.forEach(word => { + if (!index[word]) index[word] = 0 + index[word]++ + }) + return Object.keys(index).map(key => { + return { + word: key, + count: index[key] + } + }) + } + static getRandomString(length = 30, letters = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".split(""), seed = Date.now()) { + let str = "" + const randFn = Utils._getPseudoRandom0to1FloatGenerator(seed) + while (length) { + str += letters[Math.round(Math.min(randFn() * letters.length, letters.length - 1))] + length-- + } + return str + } + // todo: add seed! + static makeRandomParticles(lines = 1000, seed = Date.now()) { + let str = "" + let letters = " 123abc".split("") + const randFn = Utils._getPseudoRandom0to1FloatGenerator(seed) + while (lines) { + let indent = " ".repeat(Math.round(randFn() * 6)) + let bit = indent + let rand = Math.floor(randFn() * 30) + while (rand) { + bit += letters[Math.round(Math.min(randFn() * letters.length, letters.length - 1))] + rand-- + } + bit += "\n" + str += bit + lines-- + } + return str + } + // adapted from https://gist.github.com/blixt/f17b47c62508be59987b + // 1993 Park-Miller LCG + static _getPseudoRandom0to1FloatGenerator(seed) { + return function () { + seed = Math.imul(48271, seed) | 0 % 2147483647 + return (seed & 2147483647) / 2147483648 + } + } + static sampleWithoutReplacement(population = [], quantity, seed) { + const prng = this._getPseudoRandom0to1FloatGenerator(seed) + const sampled = {} + const populationSize = population.length + if (quantity >= populationSize) return population.slice(0) + const picked = [] + while (picked.length < quantity) { + const index = Math.floor(prng() * populationSize) + if (sampled[index]) continue + sampled[index] = true + picked.push(population[index]) + } + return picked + } + static arrayToMap(arr) { + const map = {} + arr.forEach(val => (map[val] = true)) + return map + } + static _replaceNonAlphaNumericCharactersWithCharCodes(str) { + return str + .replace(/[^a-zA-Z0-9]/g, sub => { + return "_" + sub.charCodeAt(0).toString() + }) + .replace(/^([0-9])/, "number$1") + } + static mapValues(object, fn) { + const result = {} + Object.keys(object).forEach(key => { + result[key] = fn(key) + }) + return result + } + static javascriptTableWithHeaderRowToObjects(dataTable) { + dataTable = dataTable.slice() + const header = dataTable.shift() + return dataTable.map(row => { + const obj = {} + header.forEach((colName, index) => (obj[colName] = row[index])) + return obj + }) + } + static interweave(arrayOfArrays) { + const lineCount = Math.max(...arrayOfArrays.map(arr => arr.length)) + const totalArrays = arrayOfArrays.length + const result = [] + arrayOfArrays.forEach((lineArray, arrayIndex) => { + for (let lineIndex = 0; lineIndex < lineCount; lineIndex++) { + result[lineIndex * totalArrays + arrayIndex] = lineArray[lineIndex] + } + }) + return result + } + static makeSortByFn(accessorOrAccessors) { + const arrayOfFns = Array.isArray(accessorOrAccessors) ? accessorOrAccessors : [accessorOrAccessors] + return (objectA, objectB) => { + const particleAFirst = -1 + const particleBFirst = 1 + const accessor = arrayOfFns[0] // todo: handle accessors + const av = accessor(objectA) + const bv = accessor(objectB) + let result = av < bv ? particleAFirst : av > bv ? particleBFirst : 0 + if (av === undefined && bv !== undefined) result = particleAFirst + else if (bv === undefined && av !== undefined) result = particleBFirst + return result + } + } + static _makeGraphSortFunctionFromGraph(idAccessor, graph) { + return (particleA, particleB) => { + const particleAFirst = -1 + const particleBFirst = 1 + const particleAUniqueId = idAccessor(particleA) + const particleBUniqueId = idAccessor(particleB) + const particleAExtendsParticleB = graph[particleAUniqueId].has(particleBUniqueId) + const particleBExtendsParticleA = graph[particleBUniqueId].has(particleAUniqueId) + if (particleAExtendsParticleB) return particleBFirst + else if (particleBExtendsParticleA) return particleAFirst + const particleAExtendsSomething = graph[particleAUniqueId].size > 1 + const particleBExtendsSomething = graph[particleBUniqueId].size > 1 + if (!particleAExtendsSomething && particleBExtendsSomething) return particleAFirst + else if (!particleBExtendsSomething && particleAExtendsSomething) return particleBFirst + if (particleAUniqueId > particleBUniqueId) return particleBFirst + else if (particleAUniqueId < particleBUniqueId) return particleAFirst + return 0 + } + } + static removeAll(str, needle) { + return str.split(needle).join("") + } + static _makeGraphSortFunction(idAccessor, extendsIdAccessor) { + return (particleA, particleB) => { + // -1 === a before b + const particleAUniqueId = idAccessor(particleA) + const particleAExtends = extendsIdAccessor(particleA) + const particleBUniqueId = idAccessor(particleB) + const particleBExtends = extendsIdAccessor(particleB) + const particleAExtendsParticleB = particleAExtends === particleBUniqueId + const particleBExtendsParticleA = particleBExtends === particleAUniqueId + const particleAFirst = -1 + const particleBFirst = 1 + if (!particleAExtends && !particleBExtends) { + // If neither extends, sort by firstWord + if (particleAUniqueId > particleBUniqueId) return particleBFirst + else if (particleAUniqueId < particleBUniqueId) return particleAFirst + return 0 + } + // If only one extends, the other comes first + else if (!particleAExtends) return particleAFirst + else if (!particleBExtends) return particleBFirst + // If A extends B, B should come first + if (particleAExtendsParticleB) return particleBFirst + else if (particleBExtendsParticleA) return particleAFirst + // Sort by what they extend + if (particleAExtends > particleBExtends) return particleBFirst + else if (particleAExtends < particleBExtends) return particleAFirst + // Finally sort by firstWord + if (particleAUniqueId > particleBUniqueId) return particleBFirst + else if (particleAUniqueId < particleBUniqueId) return particleAFirst + // Should never hit this, unless we have a duplicate line. + return 0 + } + } +} +Utils.Timer = Timer +//http://stackoverflow.com/questions/37684/how-to-replace-plain-urls-with-links#21925491 +Utils.linkify = (text, target = "_blank") => { + let replacedText + let replacePattern1 + let replacePattern2 + let replacePattern3 + //URLs starting with http://, https://, or ftp:// + replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z\(\)0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+\(\)&@#\/%=~_|])/gim + replacedText = text.replace(replacePattern1, `$1`) + //URLs starting with "www." (without // before it, or it'd re-link the ones done above). + replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim + replacedText = replacedText.replace(replacePattern2, `$1$2`) + //Change email addresses to mailto:: links. + replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim + replacedText = replacedText.replace(replacePattern3, '$1') + return replacedText +} +// todo: switch algo to: http://indiegamr.com/generate-repeatable-random-numbers-in-js/? +Utils.makeSemiRandomFn = (seed = Date.now()) => { + return () => { + const semiRand = Math.sin(seed++) * 10000 + return semiRand - Math.floor(semiRand) + } +} +Utils.randomUniformInt = (min, max, seed = Date.now()) => { + return Math.floor(Utils.randomUniformFloat(min, max, seed)) +} +Utils.randomUniformFloat = (min, max, seed = Date.now()) => { + const randFn = Utils.makeSemiRandomFn(seed) + return min + (max - min) * randFn() +} +Utils.getRange = (startIndex, endIndexExclusive, increment = 1) => { + const range = [] + for (let index = startIndex; index < endIndexExclusive; index = index + increment) { + range.push(index) + } + return range +} +Utils.MAX_INT = Math.pow(2, 32) - 1 +window.Utils = Utils + + +let _scrollsdkLatestTime = 0 +let _scrollsdkMinTimeIncrement = 0.000000000001 +class AbstractParticle { + _getProcessTimeInMilliseconds() { + // We add this loop to restore monotonically increasing .now(): + // https://developer.mozilla.org/en-US/docs/Web/API/Performance/now + let time = performance.now() + while (time <= _scrollsdkLatestTime) { + if (time === time + _scrollsdkMinTimeIncrement) + // Some browsers have different return values for perf.now() + _scrollsdkMinTimeIncrement = 10 * _scrollsdkMinTimeIncrement + time += _scrollsdkMinTimeIncrement + } + _scrollsdkLatestTime = time + return time + } +} +var FileFormat +;(function (FileFormat) { + FileFormat["csv"] = "csv" + FileFormat["tsv"] = "tsv" + FileFormat["particles"] = "particles" +})(FileFormat || (FileFormat = {})) +const TN_WORD_BREAK_SYMBOL = " " +const TN_EDGE_SYMBOL = " " +const TN_NODE_BREAK_SYMBOL = "\n" +class AbstractParticleEvent { + constructor(targetParticle) { + this.targetParticle = targetParticle + } +} +class ChildAddedParticleEvent extends AbstractParticleEvent {} +class ChildRemovedParticleEvent extends AbstractParticleEvent {} +class DescendantChangedParticleEvent extends AbstractParticleEvent {} +class LineChangedParticleEvent extends AbstractParticleEvent {} +class ParticleWord { + constructor(particle, cellIndex) { + this._particle = particle + this._cellIndex = cellIndex + } + replace(newWord) { + this._particle.setWord(this._cellIndex, newWord) + } + get word() { + return this._particle.getWord(this._cellIndex) + } +} +const ParticleEvents = { ChildAddedParticleEvent, ChildRemovedParticleEvent, DescendantChangedParticleEvent, LineChangedParticleEvent } +var WhereOperators +;(function (WhereOperators) { + WhereOperators["equal"] = "=" + WhereOperators["notEqual"] = "!=" + WhereOperators["lessThan"] = "<" + WhereOperators["lessThanOrEqual"] = "<=" + WhereOperators["greaterThan"] = ">" + WhereOperators["greaterThanOrEqual"] = ">=" + WhereOperators["includes"] = "includes" + WhereOperators["doesNotInclude"] = "doesNotInclude" + WhereOperators["in"] = "in" + WhereOperators["notIn"] = "notIn" + WhereOperators["empty"] = "empty" + WhereOperators["notEmpty"] = "notEmpty" +})(WhereOperators || (WhereOperators = {})) +var ParticlesConstants +;(function (ParticlesConstants) { + ParticlesConstants["extends"] = "extends" +})(ParticlesConstants || (ParticlesConstants = {})) +class ParserCombinator { + constructor(catchAllParser, firstWordMap = {}, regexTests = undefined) { + this._catchAllParser = catchAllParser + this._firstWordMap = new Map(Object.entries(firstWordMap)) + this._regexTests = regexTests + } + getFirstWordOptions() { + return Array.from(this._getFirstWordMap().keys()) + } + // todo: remove + _getFirstWordMap() { + return this._firstWordMap + } + // todo: remove + _getFirstWordMapAsObject() { + let obj = {} + const map = this._getFirstWordMap() + for (let [key, val] of map.entries()) { + obj[key] = val + } + return obj + } + _getParser(line, contextParticle, wordBreakSymbol = TN_WORD_BREAK_SYMBOL) { + return this._getFirstWordMap().get(this._getFirstWord(line, wordBreakSymbol)) || this._getParserFromRegexTests(line) || this._getCatchAllParser(contextParticle) + } + _getCatchAllParser(contextParticle) { + if (this._catchAllParser) return this._catchAllParser + const parent = contextParticle.parent + if (parent) return parent._getParser()._getCatchAllParser(parent) + return contextParticle.constructor + } + _getParserFromRegexTests(line) { + if (!this._regexTests) return undefined + const hit = this._regexTests.find(test => test.regex.test(line)) + if (hit) return hit.parser + return undefined + } + _getFirstWord(line, wordBreakSymbol) { + const firstBreak = line.indexOf(wordBreakSymbol) + return line.substr(0, firstBreak > -1 ? firstBreak : undefined) + } +} +class Particle extends AbstractParticle { + constructor(children, line, parent) { + super() + // BEGIN MUTABLE METHODS BELOw + this._particleCreationTime = this._getProcessTimeInMilliseconds() + this._parent = parent + this._setLine(line) + this._setChildren(children) + } + execute() {} + async loadRequirements(context) { + // todo: remove + await Promise.all(this.map(particle => particle.loadRequirements(context))) + } + getErrors() { + return [] + } + get lineCellTypes() { + // todo: make this any a constant + return "undefinedCellType ".repeat(this.words.length).trim() + } + isNodeJs() { + return typeof exports !== "undefined" + } + isBrowser() { + return !this.isNodeJs() + } + getOlderSiblings() { + if (this.isRoot()) return [] + return this.parent.slice(0, this.getIndex()) + } + _getClosestOlderSibling() { + const olderSiblings = this.getOlderSiblings() + return olderSiblings[olderSiblings.length - 1] + } + getYoungerSiblings() { + if (this.isRoot()) return [] + return this.parent.slice(this.getIndex() + 1) + } + getSiblings() { + if (this.isRoot()) return [] + return this.parent.filter(particle => particle !== this) + } + _getUid() { + if (!this._uid) this._uid = Particle._makeUniqueId() + return this._uid + } + // todo: rename getMother? grandMother et cetera? + get parent() { + return this._parent + } + getIndentLevel(relativeTo) { + return this._getIndentLevel(relativeTo) + } + get indentation() { + const indentLevel = this._getIndentLevel() - 1 + if (indentLevel < 0) return "" + return this.edgeSymbol.repeat(indentLevel) + } + _getTopDownArray(arr) { + this.forEach(child => { + arr.push(child) + child._getTopDownArray(arr) + }) + } + get topDownArray() { + const arr = [] + this._getTopDownArray(arr) + return arr + } + *getTopDownArrayIterator() { + for (let child of this.getChildren()) { + yield child + yield* child.getTopDownArrayIterator() + } + } + particleAtLine(lineNumber) { + let index = 0 + for (let particle of this.getTopDownArrayIterator()) { + if (lineNumber === index) return particle + index++ + } + } + get numberOfLines() { + let lineCount = 0 + for (let particle of this.getTopDownArrayIterator()) { + lineCount++ + } + return lineCount + } + _getMaxUnitsOnALine() { + let max = 0 + for (let particle of this.getTopDownArrayIterator()) { + const count = particle.words.length + particle.getIndentLevel() + if (count > max) max = count + } + return max + } + get numberOfWords() { + let wordCount = 0 + for (let particle of this.getTopDownArrayIterator()) { + wordCount += particle.words.length + } + return wordCount + } + get lineNumber() { + return this._getLineNumberRelativeTo() + } + _getLineNumber(target = this) { + if (this._cachedLineNumber) return this._cachedLineNumber + let lineNumber = 1 + for (let particle of this.root.getTopDownArrayIterator()) { + if (particle === target) return lineNumber + lineNumber++ + } + return lineNumber + } + isBlankLine() { + return !this.length && !this.getLine() + } + hasDuplicateFirstWords() { + return this.length ? new Set(this.getFirstWords()).size !== this.length : false + } + isEmpty() { + return !this.length && !this.content + } + _getLineNumberRelativeTo(relativeTo) { + if (this.isRoot(relativeTo)) return 0 + const start = relativeTo || this.root + return start._getLineNumber(this) + } + isRoot(relativeTo) { + return relativeTo === this || !this.parent + } + get root() { + return this._getRootParticle() + } + _getRootParticle(relativeTo) { + if (this.isRoot(relativeTo)) return this + return this.parent._getRootParticle(relativeTo) + } + toString(indentCount = 0, language = this) { + if (this.isRoot()) return this._childrenToString(indentCount, language) + return language.edgeSymbol.repeat(indentCount) + this.getLine(language) + (this.length ? language.particleBreakSymbol + this._childrenToString(indentCount + 1, language) : "") + } + get asString() { + return this.toString() + } + printLinesFrom(start, quantity) { + return this._printLinesFrom(start, quantity, false) + } + printLinesWithLineNumbersFrom(start, quantity) { + return this._printLinesFrom(start, quantity, true) + } + _printLinesFrom(start, quantity, printLineNumbers) { + // todo: use iterator for better perf? + const end = start + quantity + this.toString() + .split("\n") + .slice(start, end) + .forEach((line, index) => { + if (printLineNumbers) console.log(`${start + index} ${line}`) + else console.log(line) + }) + return this + } + getWord(index) { + const words = this._getWords(0) + if (index < 0) index = words.length + index + return words[index] + } + get list() { + return this.getWordsFrom(1) + } + _toHtml(indentCount) { + const path = this.getPathVector().join(" ") + const classes = { + particleLine: "particleLine", + edgeSymbol: "edgeSymbol", + particleBreakSymbol: "particleBreakSymbol", + particleChildren: "particleChildren" + } + const edge = this.edgeSymbol.repeat(indentCount) + // Set up the firstWord part of the particle + const edgeHtml = `${edge}` + const lineHtml = this._getLineHtml() + const childrenHtml = this.length ? `${this.particleBreakSymbol}` + `${this._childrenToHtml(indentCount + 1)}` : "" + return `${edgeHtml}${lineHtml}${childrenHtml}` + } + _getWords(startFrom) { + if (!this._words) this._words = this._getLine().split(this.wordBreakSymbol) + return startFrom ? this._words.slice(startFrom) : this._words + } + get words() { + return this._getWords(0) + } + doesExtend(parserId) { + return false + } + require(moduleName, filePath) { + if (!this.isNodeJs()) return window[moduleName] + return require(filePath || moduleName) + } + getWordsFrom(startFrom) { + return this._getWords(startFrom) + } + getFirstAncestor() { + const parent = this.parent + return parent.isRoot() ? this : parent.getFirstAncestor() + } + isLoaded() { + return true + } + getRunTimePhaseErrors() { + if (!this._runTimePhaseErrors) this._runTimePhaseErrors = {} + return this._runTimePhaseErrors + } + setRunTimePhaseError(phase, errorObject) { + if (errorObject === undefined) delete this.getRunTimePhaseErrors()[phase] + else this.getRunTimePhaseErrors()[phase] = errorObject + return this + } + _getJavascriptPrototypeChainUpTo(stopAtClassName = "Particle") { + // todo: cross browser test this + let constructor = this.constructor + const chain = [] + while (constructor.name !== stopAtClassName) { + chain.unshift(constructor.name) + constructor = constructor.__proto__ + } + chain.unshift(stopAtClassName) + return chain + } + _getProjectRootDir() { + return this.isRoot() ? "" : this.root._getProjectRootDir() + } + // Concat 2 particles amd return a new particle, but replace any particles + // in this particle that start with the same particle from the first particle with + // that patched version. Does not recurse. + patch(two) { + const copy = this.clone() + two.forEach(particle => { + const hit = copy.getParticle(particle.getWord(0)) + if (hit) hit.destroy() + }) + copy.concat(two) + return copy + } + getSparsity() { + const particles = this.getChildren() + const fields = this._getUnionNames() + let count = 0 + this.getChildren().forEach(particle => { + fields.forEach(field => { + if (particle.has(field)) count++ + }) + }) + return 1 - count / (particles.length * fields.length) + } + // todo: rename. what is the proper term from set/cat theory? + getBiDirectionalMaps(propertyNameOrFn, propertyNameOrFn2 = particle => particle.getWord(0)) { + const oneToTwo = {} + const twoToOne = {} + const is1Str = typeof propertyNameOrFn === "string" + const is2Str = typeof propertyNameOrFn2 === "string" + const children = this.getChildren() + this.forEach((particle, index) => { + const value1 = is1Str ? particle.get(propertyNameOrFn) : propertyNameOrFn(particle, index, children) + const value2 = is2Str ? particle.get(propertyNameOrFn2) : propertyNameOrFn2(particle, index, children) + if (value1 !== undefined) { + if (!oneToTwo[value1]) oneToTwo[value1] = [] + oneToTwo[value1].push(value2) + } + if (value2 !== undefined) { + if (!twoToOne[value2]) twoToOne[value2] = [] + twoToOne[value2].push(value1) + } + }) + return [oneToTwo, twoToOne] + } + _getWordIndexCharacterStartPosition(wordIndex) { + const xiLength = this.edgeSymbol.length + const numIndents = this._getIndentLevel() - 1 + const indentPosition = xiLength * numIndents + if (wordIndex < 1) return xiLength * (numIndents + wordIndex) + return indentPosition + this.words.slice(0, wordIndex).join(this.wordBreakSymbol).length + this.wordBreakSymbol.length + } + getParticleInScopeAtCharIndex(charIndex) { + if (this.isRoot()) return this + let wordIndex = this.getWordIndexAtCharacterIndex(charIndex) + if (wordIndex > 0) return this + let particle = this + while (wordIndex < 1) { + particle = particle.parent + wordIndex++ + } + return particle + } + getWordProperties(wordIndex) { + const start = this._getWordIndexCharacterStartPosition(wordIndex) + const word = wordIndex < 0 ? "" : this.getWord(wordIndex) + return { + startCharIndex: start, + endCharIndex: start + word.length, + word: word + } + } + fill(fill = "") { + this.topDownArray.forEach(line => { + line.words.forEach((word, index) => line.setWord(index, fill)) + }) + return this + } + getAllWordBoundaryCoordinates() { + const coordinates = [] + let lineIndex = 0 + for (let particle of this.getTopDownArrayIterator()) { + particle.getWordBoundaryCharIndices().forEach((charIndex, wordIndex) => { + coordinates.push({ + lineIndex: lineIndex, + charIndex: charIndex, + wordIndex: wordIndex + }) + }) + lineIndex++ + } + return coordinates + } + getWordBoundaryCharIndices() { + let indentLevel = this._getIndentLevel() + const wordBreakSymbolLength = this.wordBreakSymbol.length + let elapsed = indentLevel + return this.words.map((word, wordIndex) => { + const boundary = elapsed + elapsed += word.length + wordBreakSymbolLength + return boundary + }) + } + getWordIndexAtCharacterIndex(charIndex) { + // todo: is this correct thinking for handling root? + if (this.isRoot()) return 0 + const numberOfIndents = this._getIndentLevel(undefined) - 1 + // todo: probably want to rewrite this in a performant way. + const spots = [] + while (spots.length < numberOfIndents) { + spots.push(-(numberOfIndents - spots.length)) + } + this.words.forEach((word, wordIndex) => { + word.split("").forEach(letter => { + spots.push(wordIndex) + }) + spots.push(wordIndex) + }) + return spots[charIndex] + } + // Note: This currently does not return any errors resulting from "required" or "single" + getAllErrors(lineStartsAt = 1) { + const errors = [] + for (let particle of this.topDownArray) { + particle._cachedLineNumber = lineStartsAt // todo: cleanup + const errs = particle.getErrors() + errs.forEach(err => errors.push(err)) + // delete particle._cachedLineNumber + lineStartsAt++ + } + return errors + } + *getAllErrorsIterator() { + let line = 1 + for (let particle of this.getTopDownArrayIterator()) { + particle._cachedLineNumber = line + const errs = particle.getErrors() + // delete particle._cachedLineNumber + if (errs.length) yield errs + line++ + } + } + get firstWord() { + return this.words[0] + } + get content() { + const words = this.getWordsFrom(1) + return words.length ? words.join(this.wordBreakSymbol) : undefined + } + get contentWithChildren() { + // todo: deprecate + const content = this.content + return (content ? content : "") + (this.length ? this.particleBreakSymbol + this._childrenToString() : "") + } + getFirstParticle() { + return this.particleAt(0) + } + getStack() { + return this._getStack() + } + _getStack(relativeTo) { + if (this.isRoot(relativeTo)) return [] + const parent = this.parent + if (parent.isRoot(relativeTo)) return [this] + else return parent._getStack(relativeTo).concat([this]) + } + getStackString() { + return this._getStack() + .map((particle, index) => this.edgeSymbol.repeat(index) + particle.getLine()) + .join(this.particleBreakSymbol) + } + getLine(language) { + if (!this._words && !language) return this._getLine() // todo: how does this interact with "language" param? + return this.words.join((language || this).wordBreakSymbol) + } + getColumnNames() { + return this._getUnionNames() + } + getOneHot(column) { + const clone = this.clone() + const cols = Array.from(new Set(clone.getColumn(column))) + clone.forEach(particle => { + const val = particle.get(column) + particle.delete(column) + cols.forEach(col => { + particle.set(column + "_" + col, val === col ? "1" : "0") + }) + }) + return clone + } + // todo: return array? getPathArray? + _getFirstWordPath(relativeTo) { + if (this.isRoot(relativeTo)) return "" + else if (this.parent.isRoot(relativeTo)) return this.firstWord + return this.parent._getFirstWordPath(relativeTo) + this.edgeSymbol + this.firstWord + } + getFirstWordPathRelativeTo(relativeTo) { + return this._getFirstWordPath(relativeTo) + } + getFirstWordPath() { + return this._getFirstWordPath() + } + getPathVector() { + return this._getPathVector() + } + getPathVectorRelativeTo(relativeTo) { + return this._getPathVector(relativeTo) + } + _getPathVector(relativeTo) { + if (this.isRoot(relativeTo)) return [] + const path = this.parent._getPathVector(relativeTo) + path.push(this.getIndex()) + return path + } + getIndex() { + return this.parent._indexOfParticle(this) + } + isTerminal() { + return !this.length + } + _getLineHtml() { + return this.words.map((word, index) => `${Utils.stripHtml(word)}`).join(`${this.wordBreakSymbol}`) + } + _getXmlContent(indentCount) { + if (this.content !== undefined) return this.contentWithChildren + return this.length ? `${indentCount === -1 ? "" : "\n"}${this._childrenToXml(indentCount > -1 ? indentCount + 2 : -1)}${" ".repeat(indentCount)}` : "" + } + _toXml(indentCount) { + const indent = " ".repeat(indentCount) + const tag = this.firstWord + return `${indent}<${tag}>${this._getXmlContent(indentCount)}${indentCount === -1 ? "" : "\n"}` + } + _toObjectTuple() { + const content = this.content + const length = this.length + const hasChildrenNoContent = content === undefined && length + const hasContentAndHasChildren = content !== undefined && length + // If the particle has a content and a subparticle return it as a string, as + // Javascript object values can't be both a leaf and a particle. + const tupleValue = hasChildrenNoContent ? this.toObject() : hasContentAndHasChildren ? this.contentWithChildren : content + return [this.firstWord, tupleValue] + } + _indexOfParticle(needleParticle) { + let result = -1 + this.find((particle, index) => { + if (particle === needleParticle) { + result = index + return true + } + }) + return result + } + getMaxLineWidth() { + let maxWidth = 0 + for (let particle of this.getTopDownArrayIterator()) { + const lineWidth = particle.getLine().length + if (lineWidth > maxWidth) maxWidth = lineWidth + } + return maxWidth + } + toParticle() { + return new Particle(this.toString()) + } + _rightPad(newWidth, padCharacter) { + const line = this.getLine() + this.setLine(line + padCharacter.repeat(newWidth - line.length)) + return this + } + rightPad(padCharacter = " ") { + const newWidth = this.getMaxLineWidth() + this.topDownArray.forEach(particle => particle._rightPad(newWidth, padCharacter)) + return this + } + lengthen(numberOfLines) { + let linesToAdd = numberOfLines - this.numberOfLines + while (linesToAdd > 0) { + this.appendLine("") + linesToAdd-- + } + return this + } + toSideBySide(particlesOrStrings, delimiter = " ") { + particlesOrStrings = particlesOrStrings.map(particle => (particle instanceof Particle ? particle : new Particle(particle))) + const clone = this.toParticle() + const particleBreakSymbol = "\n" + let next + while ((next = particlesOrStrings.shift())) { + clone.lengthen(next.numberOfLines) + clone.rightPad() + next + .toString() + .split(particleBreakSymbol) + .forEach((line, index) => { + const particle = clone.particleAtLine(index) + particle.setLine(particle.getLine() + delimiter + line) + }) + } + return clone + } + toComparison(particle) { + const particleBreakSymbol = "\n" + const lines = particle.toString().split(particleBreakSymbol) + return new Particle( + this.toString() + .split(particleBreakSymbol) + .map((line, index) => (lines[index] === line ? "" : "x")) + .join(particleBreakSymbol) + ) + } + toBraid(particlesOrStrings) { + particlesOrStrings.unshift(this) + const particleDelimiter = this.particleBreakSymbol + return new Particle( + Utils.interweave(particlesOrStrings.map(particle => particle.toString().split(particleDelimiter))) + .map(line => (line === undefined ? "" : line)) + .join(particleDelimiter) + ) + } + getSlice(startIndexInclusive, stopIndexExclusive) { + return new Particle( + this.slice(startIndexInclusive, stopIndexExclusive) + .map(child => child.toString()) + .join("\n") + ) + } + _hasColumns(columns) { + const words = this.words + return columns.every((searchTerm, index) => searchTerm === words[index]) + } + hasWord(index, word) { + return this.getWord(index) === word + } + getParticleByColumns(...columns) { + return this.topDownArray.find(particle => particle._hasColumns(columns)) + } + getParticleByColumn(index, name) { + return this.find(particle => particle.getWord(index) === name) + } + _getParticlesByColumn(index, name) { + return this.filter(particle => particle.getWord(index) === name) + } + // todo: preserve subclasses! + select(columnNames) { + columnNames = Array.isArray(columnNames) ? columnNames : [columnNames] + const result = new Particle() + this.forEach(particle => { + const newParticle = result.appendLine(particle.getLine()) + columnNames.forEach(name => { + const valueParticle = particle.getParticle(name) + if (valueParticle) newParticle.appendParticle(valueParticle) + }) + }) + return result + } + selectionToString() { + return this.getSelectedParticles() + .map(particle => particle.toString()) + .join("\n") + } + getSelectedParticles() { + return this.topDownArray.filter(particle => particle.isSelected()) + } + clearSelection() { + this.getSelectedParticles().forEach(particle => particle.unselectParticle()) + } + // Note: this is for debugging select chains + print(message = "") { + if (message) console.log(message) + console.log(this.toString()) + return this + } + // todo: preserve subclasses! + // todo: preserve links back to parent so you could edit as normal? + where(columnName, operator, fixedValue) { + const isArray = Array.isArray(fixedValue) + const valueType = isArray ? typeof fixedValue[0] : typeof fixedValue + let parser + if (valueType === "number") parser = parseFloat + const fn = particle => { + const cell = particle.get(columnName) + const typedCell = parser ? parser(cell) : cell + if (operator === WhereOperators.equal) return fixedValue === typedCell + else if (operator === WhereOperators.notEqual) return fixedValue !== typedCell + else if (operator === WhereOperators.includes) return typedCell !== undefined && typedCell.includes(fixedValue) + else if (operator === WhereOperators.doesNotInclude) return typedCell === undefined || !typedCell.includes(fixedValue) + else if (operator === WhereOperators.greaterThan) return typedCell > fixedValue + else if (operator === WhereOperators.lessThan) return typedCell < fixedValue + else if (operator === WhereOperators.greaterThanOrEqual) return typedCell >= fixedValue + else if (operator === WhereOperators.lessThanOrEqual) return typedCell <= fixedValue + else if (operator === WhereOperators.empty) return !particle.has(columnName) + else if (operator === WhereOperators.notEmpty) return particle.has(columnName) || (cell !== "" && cell !== undefined) + else if (operator === WhereOperators.in && isArray) return fixedValue.includes(typedCell) + else if (operator === WhereOperators.notIn && isArray) return !fixedValue.includes(typedCell) + } + const result = new Particle() + this.filter(fn).forEach(particle => { + result.appendParticle(particle) + }) + return result + } + with(firstWord) { + return this.filter(particle => particle.has(firstWord)) + } + without(firstWord) { + return this.filter(particle => !particle.has(firstWord)) + } + first(quantity = 1) { + return this.limit(quantity, 0) + } + last(quantity = 1) { + return this.limit(quantity, this.length - quantity) + } + // todo: preserve subclasses! + limit(quantity, offset = 0) { + const result = new Particle() + this.getChildren() + .slice(offset, quantity + offset) + .forEach(particle => { + result.appendParticle(particle) + }) + return result + } + getChildrenFirstArray() { + const arr = [] + this._getChildrenFirstArray(arr) + return arr + } + _getChildrenFirstArray(arr) { + this.forEach(child => { + child._getChildrenFirstArray(arr) + arr.push(child) + }) + } + _getIndentLevel(relativeTo) { + return this._getStack(relativeTo).length + } + getParentFirstArray() { + const levels = this._getLevels() + const arr = [] + Object.values(levels).forEach(level => { + level.forEach(item => arr.push(item)) + }) + return arr + } + _getLevels() { + const levels = {} + this.topDownArray.forEach(particle => { + const level = particle._getIndentLevel() + if (!levels[level]) levels[level] = [] + levels[level].push(particle) + }) + return levels + } + _getChildrenArray() { + if (!this._children) this._children = [] + return this._children + } + getLines() { + return this.map(particle => particle.getLine()) + } + getChildren() { + return this._getChildrenArray().slice(0) + } + get length() { + return this._getChildrenArray().length + } + _particleAt(index) { + if (index < 0) index = this.length + index + return this._getChildrenArray()[index] + } + particleAt(indexOrIndexArray) { + if (typeof indexOrIndexArray === "number") return this._particleAt(indexOrIndexArray) + if (indexOrIndexArray.length === 1) return this._particleAt(indexOrIndexArray[0]) + const first = indexOrIndexArray[0] + const particle = this._particleAt(first) + if (!particle) return undefined + return particle.particleAt(indexOrIndexArray.slice(1)) + } + // Flatten a particle into an object like {twitter:"pldb", "twitter.followers":123}. + // Assumes you have a nested key/value list with no multiline strings. + toFlatObject(delimiter = ".") { + let newObject = {} + const { edgeSymbolRegex } = this + this.forEach((child, index) => { + newObject[child.getWord(0)] = child.content + child.topDownArray.forEach(particle => { + const newColumnName = particle.getFirstWordPathRelativeTo(this).replace(edgeSymbolRegex, delimiter) + const value = particle.content + newObject[newColumnName] = value + }) + }) + return newObject + } + _toObject() { + const obj = {} + this.forEach(particle => { + const tuple = particle._toObjectTuple() + obj[tuple[0]] = tuple[1] + }) + return obj + } + get asHtml() { + return this._childrenToHtml(0) + } + _toHtmlCubeLine(indents = 0, lineIndex = 0, planeIndex = 0) { + const getLine = (cellIndex, word = "") => + `${word}` + let cells = [] + this.words.forEach((word, index) => (word ? cells.push(getLine(index + indents, word)) : "")) + return cells.join("") + } + get asHtmlCube() { + return this.map((plane, planeIndex) => plane.topDownArray.map((line, lineIndex) => line._toHtmlCubeLine(line.getIndentLevel() - 2, lineIndex, planeIndex)).join("")).join("") + } + _getHtmlJoinByCharacter() { + return `${this.particleBreakSymbol}` + } + _childrenToHtml(indentCount) { + const joinBy = this._getHtmlJoinByCharacter() + return this.map(particle => particle._toHtml(indentCount)).join(joinBy) + } + _childrenToString(indentCount, language = this) { + return this.map(particle => particle.toString(indentCount, language)).join(language.particleBreakSymbol) + } + childrenToString(indentCount = 0) { + return this._childrenToString(indentCount) + } + // todo: implement + _getChildJoinCharacter() { + return "\n" + } + format() { + this.forEach(child => child.format()) + return this + } + compile() { + return this.map(child => child.compile()).join(this._getChildJoinCharacter()) + } + get asXml() { + return this._childrenToXml(0) + } + toDisk(path) { + if (!this.isNodeJs()) throw new Error("This method only works in Node.js") + const format = Particle._getFileFormat(path) + const formats = { + particles: particle => particle.toString(), + csv: particle => particle.asCsv, + tsv: particle => particle.asTsv + } + this.require("fs").writeFileSync(path, formats[format](this), "utf8") + return this + } + _lineToYaml(indentLevel, listTag = "") { + let prefix = " ".repeat(indentLevel) + if (listTag && indentLevel > 1) prefix = " ".repeat(indentLevel - 2) + listTag + " " + return prefix + `${this.firstWord}:` + (this.content ? " " + this.content : "") + } + _isYamlList() { + return this.hasDuplicateFirstWords() + } + get asYaml() { + return `%YAML 1.2 +---\n${this._childrenToYaml(0).join("\n")}` + } + _childrenToYaml(indentLevel) { + if (this._isYamlList()) return this._childrenToYamlList(indentLevel) + else return this._childrenToYamlAssociativeArray(indentLevel) + } + // if your code-to-be-yaml has a list of associative arrays of type N and you don't + // want the type N to print + _collapseYamlLine() { + return false + } + _toYamlListElement(indentLevel) { + const children = this._childrenToYaml(indentLevel + 1) + if (this._collapseYamlLine()) { + if (indentLevel > 1) return children.join("\n").replace(" ".repeat(indentLevel), " ".repeat(indentLevel - 2) + "- ") + return children.join("\n") + } else { + children.unshift(this._lineToYaml(indentLevel, "-")) + return children.join("\n") + } + } + _childrenToYamlList(indentLevel) { + return this.map(particle => particle._toYamlListElement(indentLevel + 2)) + } + _toYamlAssociativeArrayElement(indentLevel) { + const children = this._childrenToYaml(indentLevel + 1) + children.unshift(this._lineToYaml(indentLevel)) + return children.join("\n") + } + _childrenToYamlAssociativeArray(indentLevel) { + return this.map(particle => particle._toYamlAssociativeArrayElement(indentLevel)) + } + get asJsonSubset() { + return JSON.stringify(this.toObject(), null, " ") + } + _toObjectForSerialization() { + return this.length + ? { + cells: this.words, + children: this.map(child => child._toObjectForSerialization()) + } + : { + cells: this.words + } + } + get asJson() { + return JSON.stringify({ children: this.map(child => child._toObjectForSerialization()) }, null, " ") + } + get asGrid() { + const WordBreakSymbol = this.wordBreakSymbol + return this.toString() + .split(this.particleBreakSymbol) + .map(line => line.split(WordBreakSymbol)) + } + get asGridJson() { + return JSON.stringify(this.asGrid, null, 2) + } + findParticles(firstWordPath) { + // todo: can easily speed this up + const map = {} + if (!Array.isArray(firstWordPath)) firstWordPath = [firstWordPath] + firstWordPath.forEach(path => (map[path] = true)) + return this.topDownArray.filter(particle => { + if (map[particle._getFirstWordPath(this)]) return true + return false + }) + } + evalTemplateString(str) { + const that = this + return str.replace(/{([^\}]+)}/g, (match, path) => that.get(path) || "") + } + emitLogMessage(message) { + console.log(message) + } + getColumn(path) { + return this.map(particle => particle.get(path)) + } + getFiltered(fn) { + const clone = this.clone() + clone + .filter((particle, index) => !fn(particle, index)) + .forEach(particle => { + particle.destroy() + }) + return clone + } + getParticle(firstWordPath) { + return this._getParticleByPath(firstWordPath) + } + getFrom(prefix) { + const hit = this.filter(particle => particle.getLine().startsWith(prefix))[0] + if (hit) return hit.getLine().substr((prefix + this.wordBreakSymbol).length) + } + get(firstWordPath) { + const particle = this._getParticleByPath(firstWordPath) + return particle === undefined ? undefined : particle.content + } + getOneOf(keys) { + for (let i = 0; i < keys.length; i++) { + const value = this.get(keys[i]) + if (value) return value + } + return "" + } + pick(fields) { + const newParticle = new Particle(this.toString()) // todo: why not clone? + const map = Utils.arrayToMap(fields) + newParticle.particleAt(0).forEach(particle => { + if (!map[particle.getWord(0)]) particle.destroy() + }) + return newParticle + } + getParticlesByGlobPath(query) { + return this._getParticlesByGlobPath(query) + } + _getParticlesByGlobPath(globPath) { + const edgeSymbol = this.edgeSymbol + if (!globPath.includes(edgeSymbol)) { + if (globPath === "*") return this.getChildren() + return this.filter(particle => particle.firstWord === globPath) + } + const parts = globPath.split(edgeSymbol) + const current = parts.shift() + const rest = parts.join(edgeSymbol) + const matchingParticles = current === "*" ? this.getChildren() : this.filter(child => child.firstWord === current) + return [].concat.apply( + [], + matchingParticles.map(particle => particle._getParticlesByGlobPath(rest)) + ) + } + _getParticleByPath(firstWordPath) { + const edgeSymbol = this.edgeSymbol + if (!firstWordPath.includes(edgeSymbol)) { + const index = this.indexOfLast(firstWordPath) + return index === -1 ? undefined : this._particleAt(index) + } + const parts = firstWordPath.split(edgeSymbol) + const current = parts.shift() + const currentParticle = this._getChildrenArray()[this._getIndex()[current]] + return currentParticle ? currentParticle._getParticleByPath(parts.join(edgeSymbol)) : undefined + } + get next() { + if (this.isRoot()) return this + const index = this.getIndex() + const parent = this.parent + const length = parent.length + const next = index + 1 + return next === length ? parent._getChildrenArray()[0] : parent._getChildrenArray()[next] + } + get previous() { + if (this.isRoot()) return this + const index = this.getIndex() + const parent = this.parent + const length = parent.length + const prev = index - 1 + return prev === -1 ? parent._getChildrenArray()[length - 1] : parent._getChildrenArray()[prev] + } + _getUnionNames() { + if (!this.length) return [] + const obj = {} + this.forEach(particle => { + if (!particle.length) return undefined + particle.forEach(particle => { + obj[particle.firstWord] = 1 + }) + }) + return Object.keys(obj) + } + getAncestorParticlesByInheritanceViaExtendsKeyword(key) { + const ancestorParticles = this._getAncestorParticles( + (particle, id) => particle._getParticlesByColumn(0, id), + particle => particle.get(key), + this + ) + ancestorParticles.push(this) + return ancestorParticles + } + // Note: as you can probably tell by the name of this method, I don't recommend using this as it will likely be replaced by something better. + getAncestorParticlesByInheritanceViaColumnIndices(thisColumnNumber, extendsColumnNumber) { + const ancestorParticles = this._getAncestorParticles( + (particle, id) => particle._getParticlesByColumn(thisColumnNumber, id), + particle => particle.getWord(extendsColumnNumber), + this + ) + ancestorParticles.push(this) + return ancestorParticles + } + _getAncestorParticles(getPotentialParentParticlesByIdFn, getParentIdFn, cannotContainParticle) { + const parentId = getParentIdFn(this) + if (!parentId) return [] + const potentialParentParticles = getPotentialParentParticlesByIdFn(this.parent, parentId) + if (!potentialParentParticles.length) throw new Error(`"${this.getLine()} tried to extend "${parentId}" but "${parentId}" not found.`) + if (potentialParentParticles.length > 1) throw new Error(`Invalid inheritance paths. Multiple unique ids found for "${parentId}"`) + const parentParticle = potentialParentParticles[0] + // todo: detect loops + if (parentParticle === cannotContainParticle) throw new Error(`Loop detected between '${this.getLine()}' and '${parentParticle.getLine()}'`) + const ancestorParticles = parentParticle._getAncestorParticles(getPotentialParentParticlesByIdFn, getParentIdFn, cannotContainParticle) + ancestorParticles.push(parentParticle) + return ancestorParticles + } + pathVectorToFirstWordPath(pathVector) { + const path = pathVector.slice() // copy array + const names = [] + let particle = this + while (path.length) { + if (!particle) return names + names.push(particle.particleAt(path[0]).firstWord) + particle = particle.particleAt(path.shift()) + } + return names + } + toStringWithLineNumbers() { + return this.toString() + .split("\n") + .map((line, index) => `${index + 1} ${line}`) + .join("\n") + } + get asCsv() { + return this.toDelimited(",") + } + _getTypes(header) { + const matrix = this._getMatrix(header) + const types = header.map(i => "int") + matrix.forEach(row => { + row.forEach((value, index) => { + const type = types[index] + if (type === "string") return 1 + if (value === undefined || value === "") return 1 + if (type === "float") { + if (value.match(/^\-?[0-9]*\.?[0-9]*$/)) return 1 + types[index] = "string" + } + if (value.match(/^\-?[0-9]+$/)) return 1 + types[index] = "string" + }) + }) + return types + } + toDataTable(header = this._getUnionNames()) { + const types = this._getTypes(header) + const parsers = { + string: str => str, + float: parseFloat, + int: parseInt + } + const cellFn = (cellValue, rowIndex, columnIndex) => (rowIndex ? parsers[types[columnIndex]](cellValue) : cellValue) + const arrays = this._toArrays(header, cellFn) + arrays.rows.unshift(arrays.header) + return arrays.rows + } + toDelimited(delimiter, header = this._getUnionNames(), escapeSpecialChars = true) { + const regex = new RegExp(`(\\n|\\"|\\${delimiter})`) + const cellFn = (str, row, column) => (!str.toString().match(regex) ? str : `"` + str.replace(/\"/g, `""`) + `"`) + return this._toDelimited(delimiter, header, escapeSpecialChars ? cellFn : str => str) + } + _getMatrix(columns) { + const matrix = [] + this.forEach(child => { + const row = [] + columns.forEach(col => { + row.push(child.get(col)) + }) + matrix.push(row) + }) + return matrix + } + _toArrays(columnNames, cellFn) { + const skipHeaderRow = 1 + const header = columnNames.map((columnName, index) => cellFn(columnName, 0, index)) + const rows = this.map((particle, rowNumber) => + columnNames.map((columnName, columnIndex) => { + const childParticle = particle.getParticle(columnName) + const content = childParticle ? childParticle.contentWithChildren : "" + return cellFn(content, rowNumber + skipHeaderRow, columnIndex) + }) + ) + return { + rows, + header + } + } + _toDelimited(delimiter, header, cellFn) { + const data = this._toArrays(header, cellFn) + return data.header.join(delimiter) + "\n" + data.rows.map(row => row.join(delimiter)).join("\n") + } + get asTable() { + // Output a table for printing + return this._toTable(100, false) + } + toFormattedTable(maxCharactersPerColumn, alignRight = false) { + return this._toTable(maxCharactersPerColumn, alignRight) + } + _toTable(maxCharactersPerColumn, alignRight = false) { + const header = this._getUnionNames() + // Set initial column widths + const widths = header.map(col => (col.length > maxCharactersPerColumn ? maxCharactersPerColumn : col.length)) + // Expand column widths if needed + this.forEach(particle => { + if (!particle.length) return true + header.forEach((col, index) => { + const cellValue = particle.get(col) + if (!cellValue) return true + const length = cellValue.toString().length + if (length > widths[index]) widths[index] = length > maxCharactersPerColumn ? maxCharactersPerColumn : length + }) + }) + const cellFn = (cellText, row, col) => { + const width = widths[col] + // Strip newlines in fixedWidth output + const cellValue = cellText.toString().replace(/\n/g, "\\n") + const cellLength = cellValue.length + if (cellLength > width) return cellValue.substr(0, width) + "..." + const padding = " ".repeat(width - cellLength) + return alignRight ? padding + cellValue : cellValue + padding + } + return this._toDelimited(" ", header, cellFn) + } + get asSsv() { + return this.toDelimited(" ") + } + get asOutline() { + return this._toOutline(particle => particle.getLine()) + } + toMappedOutline(particleFn) { + return this._toOutline(particleFn) + } + // Adapted from: https://github.com/notatestuser/treeify.js + _toOutline(particleFn) { + const growBranch = (outlineParticle, last, lastStates, particleFn, callback) => { + let lastStatesCopy = lastStates.slice(0) + const particle = outlineParticle.particle + if (lastStatesCopy.push([outlineParticle, last]) && lastStates.length > 0) { + let line = "" + // firstWordd on the "was last element" states of whatever we're nested within, + // we need to append either blankness or a branch to our line + lastStates.forEach((lastState, idx) => { + if (idx > 0) line += lastState[1] ? " " : "│" + }) + // the prefix varies firstWordd on whether the key contains something to show and + // whether we're dealing with the last element in this collection + // the extra "-" just makes things stand out more. + line += (last ? "└" : "├") + particleFn(particle) + callback(line) + } + if (!particle) return + const length = particle.length + let index = 0 + particle.forEach(particle => { + let lastKey = ++index === length + growBranch({ particle: particle }, lastKey, lastStatesCopy, particleFn, callback) + }) + } + let output = "" + growBranch({ particle: this }, false, [], particleFn, line => (output += line + "\n")) + return output + } + copyTo(particle, index) { + return particle._insertLineAndChildren(this.getLine(), this.childrenToString(), index) + } + // Note: Splits using a positive lookahead + // this.split("foo").join("\n") === this.toString() + split(firstWord) { + const constructor = this.constructor + const ParticleBreakSymbol = this.particleBreakSymbol + const WordBreakSymbol = this.wordBreakSymbol + // todo: cleanup. the escaping is wierd. + return this.toString() + .split(new RegExp(`\\${ParticleBreakSymbol}(?=${firstWord}(?:${WordBreakSymbol}|\\${ParticleBreakSymbol}))`, "g")) + .map(str => new constructor(str)) + } + get asMarkdownTable() { + return this.toMarkdownTableAdvanced(this._getUnionNames(), val => val) + } + toMarkdownTableAdvanced(columns, formatFn) { + const matrix = this._getMatrix(columns) + const empty = columns.map(col => "-") + matrix.unshift(empty) + matrix.unshift(columns) + const lines = matrix.map((row, rowIndex) => { + const formattedValues = row.map((val, colIndex) => formatFn(val, rowIndex, colIndex)) + return `|${formattedValues.join("|")}|` + }) + return lines.join("\n") + } + get asTsv() { + return this.toDelimited("\t") + } + get particleBreakSymbol() { + return TN_NODE_BREAK_SYMBOL + } + get wordBreakSymbol() { + return TN_WORD_BREAK_SYMBOL + } + get edgeSymbolRegex() { + return new RegExp(this.edgeSymbol, "g") + } + get particleBreakSymbolRegex() { + return new RegExp(this.particleBreakSymbol, "g") + } + get edgeSymbol() { + return TN_EDGE_SYMBOL + } + _textToContentAndChildrenTuple(text) { + const lines = text.split(this.particleBreakSymbolRegex) + const firstLine = lines.shift() + const children = !lines.length + ? undefined + : lines + .map(line => (line.substr(0, 1) === this.edgeSymbol ? line : this.edgeSymbol + line)) + .map(line => line.substr(1)) + .join(this.particleBreakSymbol) + return [firstLine, children] + } + _getLine() { + return this._line + } + _setLine(line = "") { + this._line = line + if (this._words) delete this._words + return this + } + _clearChildren() { + this._deleteByIndexes(Utils.getRange(0, this.length)) + delete this._children + return this + } + _setChildren(content, circularCheckArray) { + this._clearChildren() + if (!content) return this + // set from string + if (typeof content === "string") { + this._appendChildrenFromString(content) + return this + } + // set from particle + if (content instanceof Particle) { + content.forEach(particle => this._insertLineAndChildren(particle.getLine(), particle.childrenToString())) + return this + } + // If we set from object, create an array of inserted objects to avoid circular loops + if (!circularCheckArray) circularCheckArray = [content] + return this._setFromObject(content, circularCheckArray) + } + _setFromObject(content, circularCheckArray) { + for (let firstWord in content) { + if (!content.hasOwnProperty(firstWord)) continue + // Branch the circularCheckArray, as we only have same branch circular arrays + this._appendFromJavascriptObjectTuple(firstWord, content[firstWord], circularCheckArray.slice(0)) + } + return this + } + // todo: refactor the below. + _appendFromJavascriptObjectTuple(firstWord, content, circularCheckArray) { + const type = typeof content + let line + let children + if (content === null) line = firstWord + " " + null + else if (content === undefined) line = firstWord + else if (type === "string") { + const tuple = this._textToContentAndChildrenTuple(content) + line = firstWord + " " + tuple[0] + children = tuple[1] + } else if (type === "function") line = firstWord + " " + content.toString() + else if (type !== "object") line = firstWord + " " + content + else if (content instanceof Date) line = firstWord + " " + content.getTime().toString() + else if (content instanceof Particle) { + line = firstWord + children = new Particle(content.childrenToString(), content.getLine()) + } else if (circularCheckArray.indexOf(content) === -1) { + circularCheckArray.push(content) + line = firstWord + const length = content instanceof Array ? content.length : Object.keys(content).length + if (length) children = new Particle()._setChildren(content, circularCheckArray) + } else { + // iirc this is return early from circular + return + } + this._insertLineAndChildren(line, children) + } + _insertLineAndChildren(line, children, index = this.length) { + const parser = this._getParser()._getParser(line, this) + const newParticle = new parser(children, line, this) + const adjustedIndex = index < 0 ? this.length + index : index + this._getChildrenArray().splice(adjustedIndex, 0, newParticle) + if (this._index) this._makeIndex(adjustedIndex) + this.clearQuickCache() + return newParticle + } + _appendChildrenFromString(str) { + const lines = str.split(this.particleBreakSymbolRegex) + const parentStack = [] + let currentIndentCount = -1 + let lastParticle = this + lines.forEach(line => { + const indentCount = this._getIndentCount(line) + if (indentCount > currentIndentCount) { + currentIndentCount++ + parentStack.push(lastParticle) + } else if (indentCount < currentIndentCount) { + // pop things off stack + while (indentCount < currentIndentCount) { + parentStack.pop() + currentIndentCount-- + } + } + const lineContent = line.substr(currentIndentCount) + const parent = parentStack[parentStack.length - 1] + const parser = parent._getParser()._getParser(lineContent, parent) + lastParticle = new parser(undefined, lineContent, parent) + parent._getChildrenArray().push(lastParticle) + }) + } + _getIndex() { + // StringMap {firstWord: index} + // When there are multiple tails with the same firstWord, _index stores the last content. + // todo: change the above behavior: when a collision occurs, create an array. + return this._index || this._makeIndex() + } + getContentsArray() { + return this.map(particle => particle.content) + } + getChildrenByParser(parser) { + return this.filter(child => child instanceof parser) + } + getAncestorByParser(parser) { + if (this instanceof parser) return this + if (this.isRoot()) return undefined + const parent = this.parent + return parent instanceof parser ? parent : parent.getAncestorByParser(parser) + } + getParticleByParser(parser) { + return this.find(child => child instanceof parser) + } + indexOfLast(firstWord) { + const result = this._getIndex()[firstWord] + return result === undefined ? -1 : result + } + // todo: renmae to indexOfFirst? + indexOf(firstWord) { + if (!this.has(firstWord)) return -1 + const length = this.length + const particles = this._getChildrenArray() + for (let index = 0; index < length; index++) { + if (particles[index].firstWord === firstWord) return index + } + } + // todo: rename this. it is a particular type of object. + toObject() { + return this._toObject() + } + getFirstWords() { + return this.map(particle => particle.firstWord) + } + _makeIndex(startAt = 0) { + if (!this._index || !startAt) this._index = {} + const particles = this._getChildrenArray() + const newIndex = this._index + const length = particles.length + for (let index = startAt; index < length; index++) { + newIndex[particles[index].firstWord] = index + } + return newIndex + } + _childrenToXml(indentCount) { + return this.map(particle => particle._toXml(indentCount)).join("") + } + _getIndentCount(str) { + let level = 0 + const edgeChar = this.edgeSymbol + while (str[level] === edgeChar) { + level++ + } + return level + } + clone(children = this.childrenToString(), line = this.getLine()) { + return new this.constructor(children, line) + } + hasFirstWord(firstWord) { + return this._hasFirstWord(firstWord) + } + has(firstWordPath) { + const edgeSymbol = this.edgeSymbol + if (!firstWordPath.includes(edgeSymbol)) return this.hasFirstWord(firstWordPath) + const parts = firstWordPath.split(edgeSymbol) + const next = this.getParticle(parts.shift()) + if (!next) return false + return next.has(parts.join(edgeSymbol)) + } + hasParticle(particle) { + const needle = particle.toString() + return this.getChildren().some(particle => particle.toString() === needle) + } + _hasFirstWord(firstWord) { + return this._getIndex()[firstWord] !== undefined + } + map(fn) { + return this.getChildren().map(fn) + } + filter(fn = item => item) { + return this.getChildren().filter(fn) + } + find(fn) { + return this.getChildren().find(fn) + } + findLast(fn) { + return this.getChildren().reverse().find(fn) + } + every(fn) { + let index = 0 + for (let particle of this.getTopDownArrayIterator()) { + if (!fn(particle, index)) return false + index++ + } + return true + } + forEach(fn) { + this.getChildren().forEach(fn) + return this + } + // Recurse if predicate passes + deepVisit(predicate) { + this.forEach(particle => { + if (predicate(particle) !== false) particle.deepVisit(predicate) + }) + } + get quickCache() { + if (!this._quickCache) this._quickCache = {} + return this._quickCache + } + getCustomIndex(key) { + if (!this.quickCache.customIndexes) this.quickCache.customIndexes = {} + const customIndexes = this.quickCache.customIndexes + if (customIndexes[key]) return customIndexes[key] + const customIndex = {} + customIndexes[key] = customIndex + this.filter(file => file.has(key)).forEach(file => { + const value = file.get(key) + if (!customIndex[value]) customIndex[value] = [] + customIndex[value].push(file) + }) + return customIndex + } + clearQuickCache() { + delete this._quickCache + } + // todo: protected? + _clearIndex() { + delete this._index + this.clearQuickCache() + } + slice(start, end) { + return this.getChildren().slice(start, end) + } + // todo: make 0 and 1 a param + getInheritanceParticles() { + const paths = {} + const result = new Particle() + this.forEach(particle => { + const key = particle.getWord(0) + const parentKey = particle.getWord(1) + const parentPath = paths[parentKey] + paths[key] = parentPath ? [parentPath, key].join(" ") : key + result.touchParticle(paths[key]) + }) + return result + } + _getGrandParent() { + return this.isRoot() || this.parent.isRoot() ? undefined : this.parent.parent + } + _getParser() { + if (!Particle._parserCombinators.has(this.constructor)) Particle._parserCombinators.set(this.constructor, this.createParserCombinator()) + return Particle._parserCombinators.get(this.constructor) + } + createParserCombinator() { + return new ParserCombinator(this.constructor) + } + static _makeUniqueId() { + if (this._uniqueId === undefined) this._uniqueId = 0 + this._uniqueId++ + return this._uniqueId + } + static _getFileFormat(path) { + const format = path.split(".").pop() + return FileFormat[format] ? format : FileFormat.particles + } + getLineModifiedTime() { + return this._lineModifiedTime || this._particleCreationTime + } + getChildArrayModifiedTime() { + return this._childArrayModifiedTime || this._particleCreationTime + } + _setChildArrayMofifiedTime(value) { + this._childArrayModifiedTime = value + return this + } + getLineOrChildrenModifiedTime() { + return Math.max( + this.getLineModifiedTime(), + this.getChildArrayModifiedTime(), + Math.max.apply( + null, + this.map(child => child.getLineOrChildrenModifiedTime()) + ) + ) + } + _setVirtualParentParticle(particle) { + this._virtualParentParticle = particle + return this + } + _getVirtualParentParticle() { + return this._virtualParentParticle + } + _setVirtualAncestorParticlesByInheritanceViaColumnIndicesAndThenExpand(particles, thisIdColumnNumber, extendsIdColumnNumber) { + const map = {} + for (let particle of particles) { + const particleId = particle.getWord(thisIdColumnNumber) + if (map[particleId]) throw new Error(`Tried to define a particle with id "${particleId}" but one is already defined.`) + map[particleId] = { + particleId: particleId, + particle: particle, + parentId: particle.getWord(extendsIdColumnNumber) + } + } + // Add parent Particles + Object.values(map).forEach(particleInfo => { + const parentId = particleInfo.parentId + const parentParticle = map[parentId] + if (parentId && !parentParticle) throw new Error(`Particle "${particleInfo.particleId}" tried to extend "${parentId}" but "${parentId}" not found.`) + if (parentId) particleInfo.particle._setVirtualParentParticle(parentParticle.particle) + }) + particles.forEach(particle => particle._expandFromVirtualParentParticle()) + return this + } + _expandFromVirtualParentParticle() { + if (this._isVirtualExpanded) return this + this._isExpanding = true + let parentParticle = this._getVirtualParentParticle() + if (parentParticle) { + if (parentParticle._isExpanding) throw new Error(`Loop detected: '${this.getLine()}' is the ancestor of one of its ancestors.`) + parentParticle._expandFromVirtualParentParticle() + const clone = this.clone() + this._setChildren(parentParticle.childrenToString()) + this.extend(clone) + } + this._isExpanding = false + this._isVirtualExpanded = true + } + // todo: solve issue related to whether extend should overwrite or append. + _expandChildren(thisIdColumnNumber, extendsIdColumnNumber, childrenThatNeedExpanding = this.getChildren()) { + return this._setVirtualAncestorParticlesByInheritanceViaColumnIndicesAndThenExpand(childrenThatNeedExpanding, thisIdColumnNumber, extendsIdColumnNumber) + } + // todo: add more testing. + // todo: solve issue with where extend should overwrite or append + // todo: should take a parsers? to decide whether to overwrite or append. + // todo: this is slow. + extend(particleOrStr) { + const particle = particleOrStr instanceof Particle ? particleOrStr : new Particle(particleOrStr) + const usedFirstWords = new Set() + particle.forEach(sourceParticle => { + const firstWord = sourceParticle.firstWord + let targetParticle + const isAnArrayNotMap = usedFirstWords.has(firstWord) + if (!this.has(firstWord)) { + usedFirstWords.add(firstWord) + this.appendLineAndChildren(sourceParticle.getLine(), sourceParticle.childrenToString()) + return true + } + if (isAnArrayNotMap) targetParticle = this.appendLine(sourceParticle.getLine()) + else { + targetParticle = this.touchParticle(firstWord).setContent(sourceParticle.content) + usedFirstWords.add(firstWord) + } + if (sourceParticle.length) targetParticle.extend(sourceParticle) + }) + return this + } + lastParticle() { + return this.getChildren()[this.length - 1] + } + expandLastFromTopMatter() { + const clone = this.clone() + const map = new Map() + const lastParticle = clone.lastParticle() + lastParticle.getOlderSiblings().forEach(particle => map.set(particle.getWord(0), particle)) + lastParticle.topDownArray.forEach(particle => { + const replacement = map.get(particle.getWord(0)) + if (!replacement) return + particle.replaceParticle(str => replacement.toString()) + }) + return lastParticle + } + macroExpand(macroDefinitionWord, macroUsageWord) { + const clone = this.clone() + const defs = clone.findParticles(macroDefinitionWord) + const allUses = clone.findParticles(macroUsageWord) + const wordBreakSymbol = clone.wordBreakSymbol + defs.forEach(def => { + const macroName = def.getWord(1) + const uses = allUses.filter(particle => particle.hasWord(1, macroName)) + const params = def.getWordsFrom(2) + const replaceFn = str => { + const paramValues = str.split(wordBreakSymbol).slice(2) + let newParticle = def.childrenToString() + params.forEach((param, index) => { + newParticle = newParticle.replace(new RegExp(param, "g"), paramValues[index]) + }) + return newParticle + } + uses.forEach(particle => { + particle.replaceParticle(replaceFn) + }) + def.destroy() + }) + return clone + } + setChildren(children) { + return this._setChildren(children) + } + _updateLineModifiedTimeAndTriggerEvent() { + this._lineModifiedTime = this._getProcessTimeInMilliseconds() + } + insertWord(index, word) { + const wi = this.wordBreakSymbol + const words = this._getLine().split(wi) + words.splice(index, 0, word) + this.setLine(words.join(wi)) + return this + } + deleteDuplicates() { + const set = new Set() + this.topDownArray.forEach(particle => { + const str = particle.toString() + if (set.has(str)) particle.destroy() + else set.add(str) + }) + return this + } + setWord(index, word) { + const wi = this.wordBreakSymbol + const words = this._getLine().split(wi) + words[index] = word + this.setLine(words.join(wi)) + return this + } + deleteChildren() { + return this._clearChildren() + } + setContent(content) { + if (content === this.content) return this + const newArray = [this.firstWord] + if (content !== undefined) { + content = content.toString() + if (content.match(this.particleBreakSymbol)) return this.setContentWithChildren(content) + newArray.push(content) + } + this._setLine(newArray.join(this.wordBreakSymbol)) + this._updateLineModifiedTimeAndTriggerEvent() + return this + } + prependSibling(line, children) { + return this.parent.insertLineAndChildren(line, children, this.getIndex()) + } + appendSibling(line, children) { + return this.parent.insertLineAndChildren(line, children, this.getIndex() + 1) + } + setContentWithChildren(text) { + // todo: deprecate + if (!text.includes(this.particleBreakSymbol)) { + this._clearChildren() + return this.setContent(text) + } + const lines = text.split(this.particleBreakSymbolRegex) + const firstLine = lines.shift() + this.setContent(firstLine) + // tood: cleanup. + const remainingString = lines.join(this.particleBreakSymbol) + const children = new Particle(remainingString) + if (!remainingString) children.appendLine("") + this.setChildren(children) + return this + } + setFirstWord(firstWord) { + return this.setWord(0, firstWord) + } + setLine(line) { + if (line === this.getLine()) return this + // todo: clear parent TMTimes + this.parent._clearIndex() + this._setLine(line) + this._updateLineModifiedTimeAndTriggerEvent() + return this + } + duplicate() { + return this.parent._insertLineAndChildren(this.getLine(), this.childrenToString(), this.getIndex() + 1) + } + trim() { + // todo: could do this so only the trimmed rows are deleted. + this.setChildren(this.childrenToString().trim()) + return this + } + destroy() { + this.parent._deleteParticle(this) + } + set(firstWordPath, text) { + return this.touchParticle(firstWordPath).setContentWithChildren(text) + } + setFromText(text) { + if (this.toString() === text) return this + const tuple = this._textToContentAndChildrenTuple(text) + this.setLine(tuple[0]) + return this._setChildren(tuple[1]) + } + setPropertyIfMissing(prop, value) { + if (this.has(prop)) return true + return this.touchParticle(prop).setContent(value) + } + setProperties(propMap) { + const props = Object.keys(propMap) + const values = Object.values(propMap) + // todo: is there a built in particle method to do this? + props.forEach((prop, index) => { + const value = values[index] + if (!value) return true + if (this.get(prop) === value) return true + this.touchParticle(prop).setContent(value) + }) + return this + } + // todo: throw error if line contains a \n + appendLine(line) { + return this._insertLineAndChildren(line) + } + appendUniqueLine(line) { + if (!this.hasLine(line)) return this.appendLine(line) + return this.findLine(line) + } + appendLineAndChildren(line, children) { + return this._insertLineAndChildren(line, children) + } + getParticlesByRegex(regex) { + const matches = [] + regex = regex instanceof RegExp ? [regex] : regex + this._getParticlesByLineRegex(matches, regex) + return matches + } + // todo: remove? + getParticlesByLinePrefixes(columns) { + const matches = [] + this._getParticlesByLineRegex( + matches, + columns.map(str => new RegExp("^" + str)) + ) + return matches + } + particlesThatStartWith(prefix) { + return this.filter(particle => particle.getLine().startsWith(prefix)) + } + _getParticlesByLineRegex(matches, regs) { + const rgs = regs.slice(0) + const reg = rgs.shift() + const candidates = this.filter(child => child.getLine().match(reg)) + if (!rgs.length) return candidates.forEach(cand => matches.push(cand)) + candidates.forEach(cand => cand._getParticlesByLineRegex(matches, rgs)) + } + concat(particle) { + if (typeof particle === "string") particle = new Particle(particle) + return particle.map(particle => this._insertLineAndChildren(particle.getLine(), particle.childrenToString())) + } + _deleteByIndexes(indexesToDelete) { + if (!indexesToDelete.length) return this + this._clearIndex() + // note: assumes indexesToDelete is in ascending order + const deletedParticles = indexesToDelete.reverse().map(index => this._getChildrenArray().splice(index, 1)[0]) + this._setChildArrayMofifiedTime(this._getProcessTimeInMilliseconds()) + return this + } + _deleteParticle(particle) { + const index = this._indexOfParticle(particle) + return index > -1 ? this._deleteByIndexes([index]) : 0 + } + reverse() { + this._clearIndex() + this._getChildrenArray().reverse() + return this + } + shift() { + if (!this.length) return null + const particle = this._getChildrenArray().shift() + return particle.copyTo(new this.constructor(), 0) + } + sort(fn) { + this._getChildrenArray().sort(fn) + this._clearIndex() + return this + } + invert() { + this.forEach(particle => particle.words.reverse()) + return this + } + _rename(oldFirstWord, newFirstWord) { + const index = this.indexOf(oldFirstWord) + if (index === -1) return this + const particle = this._getChildrenArray()[index] + particle.setFirstWord(newFirstWord) + this._clearIndex() + return this + } + // Does not recurse. + remap(map) { + this.forEach(particle => { + const firstWord = particle.firstWord + if (map[firstWord] !== undefined) particle.setFirstWord(map[firstWord]) + }) + return this + } + rename(oldFirstWord, newFirstWord) { + this._rename(oldFirstWord, newFirstWord) + return this + } + renameAll(oldName, newName) { + this.findParticles(oldName).forEach(particle => particle.setFirstWord(newName)) + return this + } + _deleteAllChildParticlesWithFirstWord(firstWord) { + if (!this.has(firstWord)) return this + const allParticles = this._getChildrenArray() + const indexesToDelete = [] + allParticles.forEach((particle, index) => { + if (particle.firstWord === firstWord) indexesToDelete.push(index) + }) + return this._deleteByIndexes(indexesToDelete) + } + delete(path = "") { + const edgeSymbol = this.edgeSymbol + if (!path.includes(edgeSymbol)) return this._deleteAllChildParticlesWithFirstWord(path) + const parts = path.split(edgeSymbol) + const nextFirstWord = parts.pop() + const targetParticle = this.getParticle(parts.join(edgeSymbol)) + return targetParticle ? targetParticle._deleteAllChildParticlesWithFirstWord(nextFirstWord) : 0 + } + deleteColumn(firstWord = "") { + this.forEach(particle => particle.delete(firstWord)) + return this + } + _getNonMaps() { + const results = this.topDownArray.filter(particle => particle.hasDuplicateFirstWords()) + if (this.hasDuplicateFirstWords()) results.unshift(this) + return results + } + replaceParticle(fn) { + const parent = this.parent + const index = this.getIndex() + const newParticles = new Particle(fn(this.toString())) + const returnedParticles = [] + newParticles.forEach((child, childIndex) => { + const newParticle = parent.insertLineAndChildren(child.getLine(), child.childrenToString(), index + childIndex) + returnedParticles.push(newParticle) + }) + this.destroy() + return returnedParticles + } + insertLineAndChildren(line, children, index) { + return this._insertLineAndChildren(line, children, index) + } + insertLine(line, index) { + return this._insertLineAndChildren(line, undefined, index) + } + prependLine(line) { + return this.insertLine(line, 0) + } + pushContentAndChildren(content, children) { + let index = this.length + while (this.has(index.toString())) { + index++ + } + const line = index.toString() + (content === undefined ? "" : this.wordBreakSymbol + content) + return this.appendLineAndChildren(line, children) + } + deleteBlanks() { + this.getChildren() + .filter(particle => particle.isBlankLine()) + .forEach(particle => particle.destroy()) + return this + } + // todo: add "globalReplace" method? Which runs a global regex or string replace on the Particle as a string? + firstWordSort(firstWordOrder) { + return this._firstWordSort(firstWordOrder) + } + deleteWordAt(wordIndex) { + const words = this.words + words.splice(wordIndex, 1) + return this.setWords(words) + } + trigger(event) { + if (this._listeners && this._listeners.has(event.constructor)) { + const listeners = this._listeners.get(event.constructor) + const listenersToRemove = [] + for (let index = 0; index < listeners.length; index++) { + const listener = listeners[index] + if (listener(event) === true) listenersToRemove.push(index) + } + listenersToRemove.reverse().forEach(index => listenersToRemove.splice(index, 1)) + } + } + triggerAncestors(event) { + if (this.isRoot()) return + const parent = this.parent + parent.trigger(event) + parent.triggerAncestors(event) + } + onLineChanged(eventHandler) { + return this._addEventListener(LineChangedParticleEvent, eventHandler) + } + onDescendantChanged(eventHandler) { + return this._addEventListener(DescendantChangedParticleEvent, eventHandler) + } + onChildAdded(eventHandler) { + return this._addEventListener(ChildAddedParticleEvent, eventHandler) + } + onChildRemoved(eventHandler) { + return this._addEventListener(ChildRemovedParticleEvent, eventHandler) + } + _addEventListener(eventClass, eventHandler) { + if (!this._listeners) this._listeners = new Map() + if (!this._listeners.has(eventClass)) this._listeners.set(eventClass, []) + this._listeners.get(eventClass).push(eventHandler) + return this + } + setWords(words) { + return this.setLine(words.join(this.wordBreakSymbol)) + } + setWordsFrom(index, words) { + this.setWords(this.words.slice(0, index).concat(words)) + return this + } + appendWord(word) { + const words = this.words + words.push(word) + return this.setWords(words) + } + _firstWordSort(firstWordOrder, secondarySortFn) { + const particleAFirst = -1 + const particleBFirst = 1 + const map = {} + firstWordOrder.forEach((word, index) => { + map[word] = index + }) + this.sort((particleA, particleB) => { + const valA = map[particleA.firstWord] + const valB = map[particleB.firstWord] + if (valA > valB) return particleBFirst + if (valA < valB) return particleAFirst + return secondarySortFn ? secondarySortFn(particleA, particleB) : 0 + }) + return this + } + _touchParticle(firstWordPathArray) { + let contextParticle = this + firstWordPathArray.forEach(firstWord => { + contextParticle = contextParticle.getParticle(firstWord) || contextParticle.appendLine(firstWord) + }) + return contextParticle + } + _touchParticleByString(str) { + str = str.replace(this.particleBreakSymbolRegex, "") // todo: do we want to do this sanitization? + return this._touchParticle(str.split(this.wordBreakSymbol)) + } + touchParticle(str) { + return this._touchParticleByString(str) + } + appendParticle(particle) { + return this.appendLineAndChildren(particle.getLine(), particle.childrenToString()) + } + hasLine(line) { + return this.getChildren().some(particle => particle.getLine() === line) + } + findLine(line) { + return this.getChildren().find(particle => particle.getLine() === line) + } + getParticlesByLine(line) { + return this.filter(particle => particle.getLine() === line) + } + toggleLine(line) { + const lines = this.getParticlesByLine(line) + if (lines.length) { + lines.map(line => line.destroy()) + return this + } + return this.appendLine(line) + } + // todo: remove? + sortByColumns(indexOrIndices) { + const indices = indexOrIndices instanceof Array ? indexOrIndices : [indexOrIndices] + const length = indices.length + this.sort((particleA, particleB) => { + const wordsA = particleA.words + const wordsB = particleB.words + for (let index = 0; index < length; index++) { + const col = indices[index] + const av = wordsA[col] + const bv = wordsB[col] + if (av === undefined) return -1 + if (bv === undefined) return 1 + if (av > bv) return 1 + else if (av < bv) return -1 + } + return 0 + }) + return this + } + getWordsAsSet() { + return new Set(this.getWordsFrom(1)) + } + appendWordIfMissing(word) { + if (this.getWordsAsSet().has(word)) return this + return this.appendWord(word) + } + // todo: check to ensure identical objects + addObjectsAsDelimited(arrayOfObjects, delimiter = Utils._chooseDelimiter(new Particle(arrayOfObjects).toString())) { + const header = Object.keys(arrayOfObjects[0]) + .join(delimiter) + .replace(/[\n\r]/g, "") + const rows = arrayOfObjects.map(item => + Object.values(item) + .join(delimiter) + .replace(/[\n\r]/g, "") + ) + return this.addUniqueRowsToNestedDelimited(header, rows) + } + setChildrenAsDelimited(particle, delimiter = Utils._chooseDelimiter(particle.toString())) { + particle = particle instanceof Particle ? particle : new Particle(particle) + return this.setChildren(particle.toDelimited(delimiter)) + } + convertChildrenToDelimited(delimiter = Utils._chooseDelimiter(this.childrenToString())) { + // todo: handle newlines!!! + return this.setChildren(this.toDelimited(delimiter)) + } + addUniqueRowsToNestedDelimited(header, rowsAsStrings) { + if (!this.length) this.appendLine(header) + // todo: this looks brittle + rowsAsStrings.forEach(row => { + if (!this.toString().includes(row)) this.appendLine(row) + }) + return this + } + shiftLeft() { + const grandParent = this._getGrandParent() + if (!grandParent) return this + const parentIndex = this.parent.getIndex() + const newParticle = grandParent.insertLineAndChildren(this.getLine(), this.length ? this.childrenToString() : undefined, parentIndex + 1) + this.destroy() + return newParticle + } + pasteText(text) { + const parent = this.parent + const index = this.getIndex() + const newParticles = new Particle(text) + const firstParticle = newParticles.particleAt(0) + if (firstParticle) { + this.setLine(firstParticle.getLine()) + if (firstParticle.length) this.setChildren(firstParticle.childrenToString()) + } else { + this.setLine("") + } + newParticles.forEach((child, childIndex) => { + if (!childIndex) + // skip first + return true + parent.insertLineAndChildren(child.getLine(), child.childrenToString(), index + childIndex) + }) + return this + } + templateToString(obj) { + // todo: compile/cache for perf? + const particle = this.clone() + particle.topDownArray.forEach(particle => { + const line = particle.getLine().replace(/{([^\}]+)}/g, (match, path) => { + const replacement = obj[path] + if (replacement === undefined) throw new Error(`In string template no match found on line "${particle.getLine()}"`) + return replacement + }) + particle.pasteText(line) + }) + return particle.toString() + } + shiftRight() { + const olderSibling = this._getClosestOlderSibling() + if (!olderSibling) return this + const newParticle = olderSibling.appendLineAndChildren(this.getLine(), this.length ? this.childrenToString() : undefined) + this.destroy() + return newParticle + } + shiftYoungerSibsRight() { + const particles = this.getYoungerSiblings() + particles.forEach(particle => particle.shiftRight()) + return this + } + sortBy(nameOrNames) { + const names = nameOrNames instanceof Array ? nameOrNames : [nameOrNames] + const length = names.length + this.sort((particleA, particleB) => { + if (!particleB.length && !particleA.length) return 0 + else if (!particleA.length) return -1 + else if (!particleB.length) return 1 + for (let index = 0; index < length; index++) { + const firstWord = names[index] + const av = particleA.get(firstWord) + const bv = particleB.get(firstWord) + if (av > bv) return 1 + else if (av < bv) return -1 + } + return 0 + }) + return this + } + selectParticle() { + this._selected = true + } + unselectParticle() { + delete this._selected + } + isSelected() { + return !!this._selected + } + async saveVersion() { + const newVersion = this.toString() + const topUndoVersion = this._getTopUndoVersion() + if (newVersion === topUndoVersion) return undefined + this._recordChange(newVersion) + this._setSavedVersion(this.toString()) + return this + } + hasUnsavedChanges() { + return this.toString() !== this._getSavedVersion() + } + async redo() { + const undoStack = this._getUndoStack() + const redoStack = this._getRedoStack() + if (!redoStack.length) return undefined + undoStack.push(redoStack.pop()) + return this._reloadFromUndoTop() + } + async undo() { + const undoStack = this._getUndoStack() + const redoStack = this._getRedoStack() + if (undoStack.length === 1) return undefined + redoStack.push(undoStack.pop()) + return this._reloadFromUndoTop() + } + _getSavedVersion() { + return this._savedVersion + } + _setSavedVersion(str) { + this._savedVersion = str + return this + } + _clearRedoStack() { + const redoStack = this._getRedoStack() + redoStack.splice(0, redoStack.length) + } + getChangeHistory() { + return this._getUndoStack().slice(0) + } + _getUndoStack() { + if (!this._undoStack) this._undoStack = [] + return this._undoStack + } + _getRedoStack() { + if (!this._redoStack) this._redoStack = [] + return this._redoStack + } + _getTopUndoVersion() { + const undoStack = this._getUndoStack() + return undoStack[undoStack.length - 1] + } + async _reloadFromUndoTop() { + this.setChildren(this._getTopUndoVersion()) + } + _recordChange(newVersion) { + this._clearRedoStack() + this._getUndoStack().push(newVersion) // todo: use diffs? + } + static fromCsv(str) { + return this.fromDelimited(str, ",", '"') + } + // todo: jeez i think we can come up with a better name than "JsonSubset" + static fromJsonSubset(str) { + return new Particle(JSON.parse(str)) + } + static serializedParticleToParticle(particle) { + const language = new Particle() + const cellDelimiter = language.wordBreakSymbol + const particleDelimiter = language.particleBreakSymbol + const line = particle.cells ? particle.cells.join(cellDelimiter) : undefined + const newParticle = new Particle(undefined, line) + if (particle.children) + particle.children.forEach(child => { + newParticle.appendParticle(this.serializedParticleToParticle(child)) + }) + return newParticle + } + static fromJson(str) { + return this.serializedParticleToParticle(JSON.parse(str)) + } + static fromGridJson(str) { + const lines = JSON.parse(str) + const language = new Particle() + const cellDelimiter = language.wordBreakSymbol + const particleDelimiter = language.particleBreakSymbol + return new Particle(lines.map(line => line.join(cellDelimiter)).join(particleDelimiter)) + } + static fromSsv(str) { + return this.fromDelimited(str, " ", '"') + } + static fromTsv(str) { + return this.fromDelimited(str, "\t", '"') + } + static fromDelimited(str, delimiter, quoteChar = '"') { + str = str.replace(/\r/g, "") // remove windows newlines if present + const rows = this._getEscapedRows(str, delimiter, quoteChar) + return this._rowsToParticle(rows, delimiter, true) + } + static _getEscapedRows(str, delimiter, quoteChar) { + return str.includes(quoteChar) ? this._strToRows(str, delimiter, quoteChar) : str.split("\n").map(line => line.split(delimiter)) + } + static fromDelimitedNoHeaders(str, delimiter, quoteChar) { + str = str.replace(/\r/g, "") // remove windows newlines if present + const rows = this._getEscapedRows(str, delimiter, quoteChar) + return this._rowsToParticle(rows, delimiter, false) + } + static _strToRows(str, delimiter, quoteChar, newLineChar = "\n") { + const rows = [[]] + const newLine = "\n" + const length = str.length + let currentCell = "" + let inQuote = str.substr(0, 1) === quoteChar + let currentPosition = inQuote ? 1 : 0 + let nextChar + let isLastChar + let currentRow = 0 + let char + let isNextCharAQuote + while (currentPosition < length) { + char = str[currentPosition] + isLastChar = currentPosition + 1 === length + nextChar = str[currentPosition + 1] + isNextCharAQuote = nextChar === quoteChar + if (inQuote) { + if (char !== quoteChar) currentCell += char + else if (isNextCharAQuote) { + // Both the current and next char are ", so the " is escaped + currentCell += nextChar + currentPosition++ // Jump 2 + } else { + // If the current char is a " and the next char is not, it's the end of the quotes + inQuote = false + if (isLastChar) rows[currentRow].push(currentCell) + } + } else { + if (char === delimiter) { + rows[currentRow].push(currentCell) + currentCell = "" + if (isNextCharAQuote) { + inQuote = true + currentPosition++ // Jump 2 + } + } else if (char === newLine) { + rows[currentRow].push(currentCell) + currentCell = "" + currentRow++ + if (nextChar) rows[currentRow] = [] + if (isNextCharAQuote) { + inQuote = true + currentPosition++ // Jump 2 + } + } else if (isLastChar) rows[currentRow].push(currentCell + char) + else currentCell += char + } + currentPosition++ + } + return rows + } + static multiply(particleA, particleB) { + const productParticle = particleA.clone() + productParticle.forEach((particle, index) => { + particle.setChildren(particle.length ? this.multiply(particle, particleB) : particleB.clone()) + }) + return productParticle + } + // Given an array return a particle + static _rowsToParticle(rows, delimiter, hasHeaders) { + const numberOfColumns = rows[0].length + const particle = new Particle() + const names = this._getHeader(rows, hasHeaders) + const rowCount = rows.length + for (let rowIndex = hasHeaders ? 1 : 0; rowIndex < rowCount; rowIndex++) { + let row = rows[rowIndex] + // If the row contains too many columns, shift the extra columns onto the last one. + // This allows you to not have to escape delimiter characters in the final column. + if (row.length > numberOfColumns) { + row[numberOfColumns - 1] = row.slice(numberOfColumns - 1).join(delimiter) + row = row.slice(0, numberOfColumns) + } else if (row.length < numberOfColumns) { + // If the row is missing columns add empty columns until it is full. + // This allows you to make including delimiters for empty ending columns in each row optional. + while (row.length < numberOfColumns) { + row.push("") + } + } + const obj = {} + row.forEach((cellValue, index) => { + obj[names[index]] = cellValue + }) + particle.pushContentAndChildren(undefined, obj) + } + return particle + } + static _initializeXmlParser() { + if (this._xmlParser) return + const windowObj = window + if (typeof windowObj.DOMParser !== "undefined") this._xmlParser = xmlStr => new windowObj.DOMParser().parseFromString(xmlStr, "text/xml") + else if (typeof windowObj.ActiveXObject !== "undefined" && new windowObj.ActiveXObject("Microsoft.XMLDOM")) { + this._xmlParser = xmlStr => { + const xmlDoc = new windowObj.ActiveXObject("Microsoft.XMLDOM") + xmlDoc.async = "false" + xmlDoc.loadXML(xmlStr) + return xmlDoc + } + } else throw new Error("No XML parser found") + } + static fromXml(str) { + this._initializeXmlParser() + const xml = this._xmlParser(str) + try { + return this._particleFromXml(xml).getParticle("children") + } catch (err) { + return this._particleFromXml(this._parseXml2(str)).getParticle("children") + } + } + static _zipObject(keys, values) { + const obj = {} + keys.forEach((key, index) => (obj[key] = values[index])) + return obj + } + static fromShape(shapeArr, rootParticle = new Particle()) { + const part = shapeArr.shift() + if (part !== undefined) { + for (let index = 0; index < part; index++) { + rootParticle.appendLine(index.toString()) + } + } + if (shapeArr.length) rootParticle.forEach(particle => Particle.fromShape(shapeArr.slice(0), particle)) + return rootParticle + } + static fromDataTable(table) { + const header = table.shift() + return new Particle(table.map(row => this._zipObject(header, row))) + } + static _parseXml2(str) { + const el = document.createElement("div") + el.innerHTML = str + return el + } + // todo: cleanup typings + static _particleFromXml(xml) { + const result = new Particle() + const children = new Particle() + // Set attributes + if (xml.attributes) { + for (let index = 0; index < xml.attributes.length; index++) { + result.set(xml.attributes[index].name, xml.attributes[index].value) + } + } + if (xml.data) children.pushContentAndChildren(xml.data) + // Set content + if (xml.childNodes && xml.childNodes.length > 0) { + for (let index = 0; index < xml.childNodes.length; index++) { + const child = xml.childNodes[index] + if (child.tagName && child.tagName.match(/parsererror/i)) throw new Error("Parse Error") + if (child.childNodes.length > 0 && child.tagName) children.appendLineAndChildren(child.tagName, this._particleFromXml(child)) + else if (child.tagName) children.appendLine(child.tagName) + else if (child.data) { + const data = child.data.trim() + if (data) children.pushContentAndChildren(data) + } + } + } + if (children.length > 0) result.touchParticle("children").setChildren(children) + return result + } + static _getHeader(rows, hasHeaders) { + const numberOfColumns = rows[0].length + const headerRow = hasHeaders ? rows[0] : [] + const WordBreakSymbol = " " + const ziRegex = new RegExp(WordBreakSymbol, "g") + if (hasHeaders) { + // Strip any WordBreakSymbols from column names in the header row. + // This makes the mapping not quite 1 to 1 if there are any WordBreakSymbols in names. + for (let index = 0; index < numberOfColumns; index++) { + headerRow[index] = headerRow[index].replace(ziRegex, "") + } + } else { + // If str has no headers, create them as 0,1,2,3 + for (let index = 0; index < numberOfColumns; index++) { + headerRow.push(index.toString()) + } + } + return headerRow + } + static nest(str, xValue) { + const ParticleBreakSymbol = TN_NODE_BREAK_SYMBOL + const WordBreakSymbol = TN_WORD_BREAK_SYMBOL + const indent = ParticleBreakSymbol + WordBreakSymbol.repeat(xValue) + return str ? indent + str.replace(/\n/g, indent) : "" + } + static fromDisk(path) { + const format = this._getFileFormat(path) + const content = require("fs").readFileSync(path, "utf8") + const methods = { + particles: content => new Particle(content), + csv: content => this.fromCsv(content), + tsv: content => this.fromTsv(content) + } + if (!methods[format]) throw new Error(`No support for '${format}'`) + return methods[format](content) + } + static fromFolder(folderPath, filepathPredicate = filepath => filepath !== ".DS_Store") { + const path = require("path") + const fs = require("fs") + const particle = new Particle() + const files = fs + .readdirSync(folderPath) + .map(filename => path.join(folderPath, filename)) + .filter(filepath => !fs.statSync(filepath).isDirectory() && filepathPredicate(filepath)) + .forEach(filePath => particle.appendLineAndChildren(filePath, fs.readFileSync(filePath, "utf8"))) + return particle + } +} +Particle._parserCombinators = new Map() +Particle.ParserCombinator = ParserCombinator +Particle.iris = `sepal_length,sepal_width,petal_length,petal_width,species +6.1,3,4.9,1.8,virginica +5.6,2.7,4.2,1.3,versicolor +5.6,2.8,4.9,2,virginica +6.2,2.8,4.8,1.8,virginica +7.7,3.8,6.7,2.2,virginica +5.3,3.7,1.5,0.2,setosa +6.2,3.4,5.4,2.3,virginica +4.9,2.5,4.5,1.7,virginica +5.1,3.5,1.4,0.2,setosa +5,3.4,1.5,0.2,setosa` +Particle.getVersion = () => "85.0.0" +class AbstractExtendibleParticle extends Particle { + _getFromExtended(firstWordPath) { + const hit = this._getParticleFromExtended(firstWordPath) + return hit ? hit.get(firstWordPath) : undefined + } + _getLineage() { + const newParticle = new Particle() + this.forEach(particle => { + const path = particle._getAncestorsArray().map(particle => particle.id) + path.reverse() + newParticle.touchParticle(path.join(TN_EDGE_SYMBOL)) + }) + return newParticle + } + // todo: be more specific with the param + _getChildrenByParserInExtended(parser) { + return Utils.flatten(this._getAncestorsArray().map(particle => particle.getChildrenByParser(parser))) + } + _getExtendedParent() { + return this._getAncestorsArray()[1] + } + _hasFromExtended(firstWordPath) { + return !!this._getParticleFromExtended(firstWordPath) + } + _getParticleFromExtended(firstWordPath) { + return this._getAncestorsArray().find(particle => particle.has(firstWordPath)) + } + _getConcatBlockStringFromExtended(firstWordPath) { + return this._getAncestorsArray() + .filter(particle => particle.has(firstWordPath)) + .map(particle => particle.getParticle(firstWordPath).childrenToString()) + .reverse() + .join("\n") + } + _doesExtend(parserId) { + return this._getAncestorSet().has(parserId) + } + _getAncestorSet() { + if (!this._cache_ancestorSet) this._cache_ancestorSet = new Set(this._getAncestorsArray().map(def => def.id)) + return this._cache_ancestorSet + } + // Note: the order is: [this, parent, grandParent, ...] + _getAncestorsArray(cannotContainParticles) { + this._initAncestorsArrayCache(cannotContainParticles) + return this._cache_ancestorsArray + } + get idThatThisExtends() { + return this.get(ParticlesConstants.extends) + } + _initAncestorsArrayCache(cannotContainParticles) { + if (this._cache_ancestorsArray) return undefined + if (cannotContainParticles && cannotContainParticles.includes(this)) throw new Error(`Loop detected: '${this.getLine()}' is the ancestor of one of its ancestors.`) + cannotContainParticles = cannotContainParticles || [this] + let ancestors = [this] + const extendedId = this.idThatThisExtends + if (extendedId) { + const parentParticle = this.idToParticleMap[extendedId] + if (!parentParticle) throw new Error(`${extendedId} not found`) + ancestors = ancestors.concat(parentParticle._getAncestorsArray(cannotContainParticles)) + } + this._cache_ancestorsArray = ancestors + } +} +class ExtendibleParticle extends AbstractExtendibleParticle { + get idToParticleMap() { + if (!this.isRoot()) return this.root.idToParticleMap + if (!this._particleMapCache) { + this._particleMapCache = {} + this.forEach(child => { + this._particleMapCache[child.id] = child + }) + } + return this._particleMapCache + } + get id() { + return this.getWord(0) + } +} +window.Particle = Particle +window.ExtendibleParticle = ExtendibleParticle +window.AbstractExtendibleParticle = AbstractExtendibleParticle +window.ParticleEvents = ParticleEvents +window.ParticleWord = ParticleWord + + +// Compiled language parsers will include these files: +const GlobalNamespaceAdditions = { + Utils: "Utils.js", + Particle: "Particle.js", + HandParsersProgram: "Parsers.js", + ParserBackedParticle: "Parsers.js" +} +var ParsersConstantsCompiler +;(function (ParsersConstantsCompiler) { + ParsersConstantsCompiler["stringTemplate"] = "stringTemplate" + ParsersConstantsCompiler["indentCharacter"] = "indentCharacter" + ParsersConstantsCompiler["catchAllCellDelimiter"] = "catchAllCellDelimiter" + ParsersConstantsCompiler["openChildren"] = "openChildren" + ParsersConstantsCompiler["joinChildrenWith"] = "joinChildrenWith" + ParsersConstantsCompiler["closeChildren"] = "closeChildren" +})(ParsersConstantsCompiler || (ParsersConstantsCompiler = {})) +var ParsersConstantsMisc +;(function (ParsersConstantsMisc) { + ParsersConstantsMisc["doNotSynthesize"] = "doNotSynthesize" +})(ParsersConstantsMisc || (ParsersConstantsMisc = {})) +var PreludeCellTypeIds +;(function (PreludeCellTypeIds) { + PreludeCellTypeIds["anyCell"] = "anyCell" + PreludeCellTypeIds["keywordCell"] = "keywordCell" + PreludeCellTypeIds["extraWordCell"] = "extraWordCell" + PreludeCellTypeIds["floatCell"] = "floatCell" + PreludeCellTypeIds["numberCell"] = "numberCell" + PreludeCellTypeIds["bitCell"] = "bitCell" + PreludeCellTypeIds["boolCell"] = "boolCell" + PreludeCellTypeIds["intCell"] = "intCell" +})(PreludeCellTypeIds || (PreludeCellTypeIds = {})) +var ParsersConstantsConstantTypes +;(function (ParsersConstantsConstantTypes) { + ParsersConstantsConstantTypes["boolean"] = "boolean" + ParsersConstantsConstantTypes["string"] = "string" + ParsersConstantsConstantTypes["int"] = "int" + ParsersConstantsConstantTypes["float"] = "float" +})(ParsersConstantsConstantTypes || (ParsersConstantsConstantTypes = {})) +var ParsersBundleFiles +;(function (ParsersBundleFiles) { + ParsersBundleFiles["package"] = "package.json" + ParsersBundleFiles["readme"] = "readme.md" + ParsersBundleFiles["indexHtml"] = "index.html" + ParsersBundleFiles["indexJs"] = "index.js" + ParsersBundleFiles["testJs"] = "test.js" +})(ParsersBundleFiles || (ParsersBundleFiles = {})) +var ParsersCellParser +;(function (ParsersCellParser) { + ParsersCellParser["prefix"] = "prefix" + ParsersCellParser["postfix"] = "postfix" + ParsersCellParser["omnifix"] = "omnifix" +})(ParsersCellParser || (ParsersCellParser = {})) +var ParsersConstants +;(function (ParsersConstants) { + // particle types + ParsersConstants["extensions"] = "extensions" + ParsersConstants["comment"] = "//" + ParsersConstants["parser"] = "parser" + ParsersConstants["cellType"] = "cellType" + ParsersConstants["parsersFileExtension"] = "parsers" + ParsersConstants["abstractParserPrefix"] = "abstract" + ParsersConstants["parserSuffix"] = "Parser" + ParsersConstants["cellTypeSuffix"] = "Cell" + // error check time + ParsersConstants["regex"] = "regex" + ParsersConstants["reservedWords"] = "reservedWords" + ParsersConstants["enumFromCellTypes"] = "enumFromCellTypes" + ParsersConstants["enum"] = "enum" + ParsersConstants["examples"] = "examples" + ParsersConstants["min"] = "min" + ParsersConstants["max"] = "max" + // baseParsers + ParsersConstants["baseParser"] = "baseParser" + ParsersConstants["blobParser"] = "blobParser" + ParsersConstants["errorParser"] = "errorParser" + // parse time + ParsersConstants["extends"] = "extends" + ParsersConstants["root"] = "root" + ParsersConstants["crux"] = "crux" + ParsersConstants["cruxFromId"] = "cruxFromId" + ParsersConstants["pattern"] = "pattern" + ParsersConstants["inScope"] = "inScope" + ParsersConstants["cells"] = "cells" + ParsersConstants["listDelimiter"] = "listDelimiter" + ParsersConstants["contentKey"] = "contentKey" + ParsersConstants["childrenKey"] = "childrenKey" + ParsersConstants["uniqueFirstWord"] = "uniqueFirstWord" + ParsersConstants["catchAllCellType"] = "catchAllCellType" + ParsersConstants["cellParser"] = "cellParser" + ParsersConstants["catchAllParser"] = "catchAllParser" + ParsersConstants["constants"] = "constants" + ParsersConstants["required"] = "required" + ParsersConstants["single"] = "single" + ParsersConstants["uniqueLine"] = "uniqueLine" + ParsersConstants["tags"] = "tags" + ParsersConstants["_rootNodeJsHeader"] = "_rootNodeJsHeader" + // default catchAll parser + ParsersConstants["BlobParser"] = "BlobParser" + ParsersConstants["DefaultRootParser"] = "DefaultRootParser" + // code + ParsersConstants["javascript"] = "javascript" + // compile time + ParsersConstants["compilerParser"] = "compiler" + ParsersConstants["compilesTo"] = "compilesTo" + // develop time + ParsersConstants["description"] = "description" + ParsersConstants["example"] = "example" + ParsersConstants["popularity"] = "popularity" + ParsersConstants["paint"] = "paint" +})(ParsersConstants || (ParsersConstants = {})) +class TypedWord extends ParticleWord { + constructor(particle, cellIndex, type) { + super(particle, cellIndex) + this._type = type + } + get type() { + return this._type + } + toString() { + return this.word + ":" + this.type + } +} +// todo: can we merge these methods into base Particle and ditch this class? +class ParserBackedParticle extends Particle { + get definition() { + if (this._definition) return this._definition + this._definition = this.isRoot() ? this.handParsersProgram : this.parent.definition.getParserDefinitionByParserId(this.constructor.name) + return this._definition + } + get rootParsersParticles() { + return this.definition.root + } + getAutocompleteResults(partialWord, cellIndex) { + return cellIndex === 0 ? this._getAutocompleteResultsForFirstWord(partialWord) : this._getAutocompleteResultsForCell(partialWord, cellIndex) + } + makeError(message) { + return new ParserDefinedError(this, message) + } + get particleIndex() { + // StringMap {firstWord: index} + // When there are multiple tails with the same firstWord, _index stores the last content. + // todo: change the above behavior: when a collision occurs, create an array. + return this._particleIndex || this._makeParticleIndex() + } + _clearIndex() { + delete this._particleIndex + return super._clearIndex() + } + _makeIndex(startAt = 0) { + if (this._particleIndex) this._makeParticleIndex(startAt) + return super._makeIndex(startAt) + } + _makeParticleIndex(startAt = 0) { + if (!this._particleIndex || !startAt) this._particleIndex = {} + const particles = this._getChildrenArray() + const newIndex = this._particleIndex + const length = particles.length + for (let index = startAt; index < length; index++) { + const particle = particles[index] + const ancestors = Array.from(particle.definition._getAncestorSet()).forEach(id => { + if (!newIndex[id]) newIndex[id] = [] + newIndex[id].push(particle) + }) + } + return newIndex + } + getChildInstancesOfParserId(parserId) { + return this.particleIndex[parserId] || [] + } + doesExtend(parserId) { + return this.definition._doesExtend(parserId) + } + _getErrorParserErrors() { + return [this.firstWord ? new UnknownParserError(this) : new BlankLineError(this)] + } + _getBlobParserCatchAllParser() { + return BlobParser + } + _getAutocompleteResultsForFirstWord(partialWord) { + const keywordMap = this.definition.firstWordMapWithDefinitions + let keywords = Object.keys(keywordMap) + if (partialWord) keywords = keywords.filter(keyword => keyword.includes(partialWord)) + return keywords + .map(keyword => { + const def = keywordMap[keyword] + if (def.suggestInAutocomplete === false) return false + const description = def.description + return { + text: keyword, + displayText: keyword + (description ? " " + description : "") + } + }) + .filter(i => i) + } + _getAutocompleteResultsForCell(partialWord, cellIndex) { + // todo: root should be [] correct? + const cell = this.parsedCells[cellIndex] + return cell ? cell.getAutoCompleteWords(partialWord) : [] + } + // note: this is overwritten by the root particle of a runtime parsers program. + // some of the magic that makes this all work. but maybe there's a better way. + get handParsersProgram() { + if (this.isRoot()) throw new Error(`Root particle without getHandParsersProgram defined.`) + return this.root.handParsersProgram + } + getRunTimeEnumOptions(cell) { + return undefined + } + getRunTimeEnumOptionsForValidation(cell) { + return this.getRunTimeEnumOptions(cell) + } + _sortParticlesByInScopeOrder() { + const parserOrder = this.definition._getMyInScopeParserIds() + if (!parserOrder.length) return this + const orderMap = {} + parserOrder.forEach((word, index) => (orderMap[word] = index)) + this.sort(Utils.makeSortByFn(runtimeParticle => orderMap[runtimeParticle.definition.parserIdFromDefinition])) + return this + } + get requiredParticleErrors() { + const errors = [] + Object.values(this.definition.firstWordMapWithDefinitions).forEach(def => { + if (def.isRequired() && !this.particleIndex[def.id]) errors.push(new MissingRequiredParserError(this, def.id)) + }) + return errors + } + get programAsCells() { + // todo: what is this? + return this.topDownArray.map(particle => { + const cells = particle.parsedCells + let indents = particle.getIndentLevel() - 1 + while (indents) { + cells.unshift(undefined) + indents-- + } + return cells + }) + } + get programWidth() { + return Math.max(...this.programAsCells.map(line => line.length)) + } + get allTypedWords() { + const words = [] + this.topDownArray.forEach(particle => particle.wordTypes.forEach((cell, index) => words.push(new TypedWord(particle, index, cell.cellTypeId)))) + return words + } + findAllWordsWithCellType(cellTypeId) { + return this.allTypedWords.filter(typedWord => typedWord.type === cellTypeId) + } + findAllParticlesWithParser(parserId) { + return this.topDownArray.filter(particle => particle.definition.parserIdFromDefinition === parserId) + } + toCellTypeParticles() { + return this.topDownArray.map(child => child.indentation + child.lineCellTypes).join("\n") + } + getParseTable(maxColumnWidth = 40) { + const particle = new Particle(this.toCellTypeParticles()) + return new Particle( + particle.topDownArray.map((particle, lineNumber) => { + const sourceParticle = this.particleAtLine(lineNumber) + const errs = sourceParticle.getErrors() + const errorCount = errs.length + const obj = { + lineNumber: lineNumber, + source: sourceParticle.indentation + sourceParticle.getLine(), + parser: sourceParticle.constructor.name, + cellTypes: particle.content, + errorCount: errorCount + } + if (errorCount) obj.errorMessages = errs.map(err => err.message).join(";") + return obj + }) + ).toFormattedTable(maxColumnWidth) + } + // Helper method for selecting potential parsers needed to update parsers file. + get invalidParsers() { + return Array.from( + new Set( + this.getAllErrors() + .filter(err => err instanceof UnknownParserError) + .map(err => err.getParticle().firstWord) + ) + ) + } + _getAllAutoCompleteWords() { + return this.getAllWordBoundaryCoordinates().map(coordinate => { + const results = this.getAutocompleteResultsAt(coordinate.lineIndex, coordinate.charIndex) + return { + lineIndex: coordinate.lineIndex, + charIndex: coordinate.charIndex, + wordIndex: coordinate.wordIndex, + word: results.word, + suggestions: results.matches + } + }) + } + toAutoCompleteCube(fillChar = "") { + const particles = [this.clone()] + const filled = this.clone().fill(fillChar) + this._getAllAutoCompleteWords().forEach(hole => { + hole.suggestions.forEach((suggestion, index) => { + if (!particles[index + 1]) particles[index + 1] = filled.clone() + particles[index + 1].particleAtLine(hole.lineIndex).setWord(hole.wordIndex, suggestion.text) + }) + }) + return new Particle(particles) + } + toAutoCompleteTable() { + return new Particle( + this._getAllAutoCompleteWords().map(result => { + result.suggestions = result.suggestions.map(particle => particle.text).join(" ") + return result + }) + ).asTable + } + getAutocompleteResultsAt(lineIndex, charIndex) { + const lineParticle = this.particleAtLine(lineIndex) || this + const particleInScope = lineParticle.getParticleInScopeAtCharIndex(charIndex) + // todo: add more tests + // todo: second param this.childrenToString() + // todo: change to getAutocomplete definitions + const wordIndex = lineParticle.getWordIndexAtCharacterIndex(charIndex) + const wordProperties = lineParticle.getWordProperties(wordIndex) + return { + startCharIndex: wordProperties.startCharIndex, + endCharIndex: wordProperties.endCharIndex, + word: wordProperties.word, + matches: particleInScope.getAutocompleteResults(wordProperties.word, wordIndex) + } + } + _sortWithParentParsersUpTop() { + const lineage = new HandParsersProgram(this.toString()).parserLineage + const rank = {} + lineage.topDownArray.forEach((particle, index) => { + rank[particle.getWord(0)] = index + }) + const particleAFirst = -1 + const particleBFirst = 1 + this.sort((particleA, particleB) => { + const particleARank = rank[particleA.getWord(0)] + const particleBRank = rank[particleB.getWord(0)] + return particleARank < particleBRank ? particleAFirst : particleBFirst + }) + return this + } + format() { + if (this.isRoot()) { + this._sortParticlesByInScopeOrder() + try { + this._sortWithParentParsersUpTop() + } catch (err) { + console.log(`Warning: ${err}`) + } + } + this.topDownArray.forEach(child => child.format()) + return this + } + getParserUsage(filepath = "") { + // returns a report on what parsers from its language the program uses + const usage = new Particle() + const handParsersProgram = this.handParsersProgram + handParsersProgram.validConcreteAndAbstractParserDefinitions.forEach(def => { + const requiredCellTypeIds = def.cellParser.getRequiredCellTypeIds() + usage.appendLine([def.parserIdFromDefinition, "line-id", "parser", requiredCellTypeIds.join(" ")].join(" ")) + }) + this.topDownArray.forEach((particle, lineNumber) => { + const stats = usage.getParticle(particle.parserId) + stats.appendLine([filepath + "-" + lineNumber, particle.words.join(" ")].join(" ")) + }) + return usage + } + toPaintParticles() { + return this.topDownArray.map(child => child.indentation + child.getLinePaints()).join("\n") + } + toDefinitionLineNumberParticles() { + return this.topDownArray.map(child => child.definition.lineNumber + " " + child.indentation + child.cellDefinitionLineNumbers.join(" ")).join("\n") + } + get asCellTypeParticlesWithParserIds() { + return this.topDownArray.map(child => child.constructor.name + this.wordBreakSymbol + child.indentation + child.lineCellTypes).join("\n") + } + toPreludeCellTypeParticlesWithParserIds() { + return this.topDownArray.map(child => child.constructor.name + this.wordBreakSymbol + child.indentation + child.getLineCellPreludeTypes()).join("\n") + } + get asParticlesWithParsers() { + return this.topDownArray.map(child => child.constructor.name + this.wordBreakSymbol + child.indentation + child.getLine()).join("\n") + } + getCellPaintAtPosition(lineIndex, wordIndex) { + this._initCellTypeCache() + const typeParticle = this._cache_paintParticles.topDownArray[lineIndex - 1] + return typeParticle ? typeParticle.getWord(wordIndex - 1) : undefined + } + _initCellTypeCache() { + const particleMTime = this.getLineOrChildrenModifiedTime() + if (this._cache_programCellTypeStringMTime === particleMTime) return undefined + this._cache_typeParticles = new Particle(this.toCellTypeParticles()) + this._cache_paintParticles = new Particle(this.toPaintParticles()) + this._cache_programCellTypeStringMTime = particleMTime + } + createParserCombinator() { + return this.isRoot() ? new Particle.ParserCombinator(BlobParser) : new Particle.ParserCombinator(this.parent._getParser()._getCatchAllParser(this.parent), {}) + } + get parserId() { + return this.definition.parserIdFromDefinition + } + get wordTypes() { + return this.parsedCells.filter(cell => cell.getWord() !== undefined) + } + get cellErrors() { + const { parsedCells } = this // todo: speedup. takes ~3s on pldb. + // todo: speedup getErrorIfAny. takes ~3s on pldb. + return parsedCells.map(check => check.getErrorIfAny()).filter(identity => identity) + } + get singleParserUsedTwiceErrors() { + const errors = [] + const parent = this.parent + const hits = parent.getChildInstancesOfParserId(this.definition.id) + if (hits.length > 1) + hits.forEach((particle, index) => { + if (particle === this) errors.push(new ParserUsedMultipleTimesError(particle)) + }) + return errors + } + get uniqueLineAppearsTwiceErrors() { + const errors = [] + const parent = this.parent + const hits = parent.getChildInstancesOfParserId(this.definition.id) + if (hits.length > 1) { + const set = new Set() + hits.forEach((particle, index) => { + const line = particle.getLine() + if (set.has(line)) errors.push(new ParserUsedMultipleTimesError(particle)) + set.add(line) + }) + } + return errors + } + get scopeErrors() { + let errors = [] + const def = this.definition + if (def.isSingle) errors = errors.concat(this.singleParserUsedTwiceErrors) // todo: speedup. takes ~1s on pldb. + if (def.isUniqueLine) errors = errors.concat(this.uniqueLineAppearsTwiceErrors) // todo: speedup. takes ~1s on pldb. + const { requiredParticleErrors } = this // todo: speedup. takes ~1.5s on pldb. + if (requiredParticleErrors.length) errors = errors.concat(requiredParticleErrors) + return errors + } + getErrors() { + return this.cellErrors.concat(this.scopeErrors) + } + get parsedCells() { + return this.definition.cellParser.getCellArray(this) + } + // todo: just make a fn that computes proper spacing and then is given a particle to print + get lineCellTypes() { + return this.parsedCells.map(slot => slot.cellTypeId).join(" ") + } + getLineCellPreludeTypes() { + return this.parsedCells + .map(slot => { + const def = slot.cellTypeDefinition + //todo: cleanup + return def ? def.preludeKindId : PreludeCellTypeIds.anyCell + }) + .join(" ") + } + getLinePaints(defaultScope = "source") { + return this.parsedCells.map(slot => slot.paint || defaultScope).join(" ") + } + get cellDefinitionLineNumbers() { + return this.parsedCells.map(cell => cell.definitionLineNumber) + } + _getCompiledIndentation() { + const indentCharacter = this.definition._getCompilerObject()[ParsersConstantsCompiler.indentCharacter] + const indent = this.indentation + return indentCharacter !== undefined ? indentCharacter.repeat(indent.length) : indent + } + _getFields() { + // fields are like cells + const fields = {} + this.forEach(particle => { + const def = particle.definition + if (def.isRequired() || def.isSingle) fields[particle.getWord(0)] = particle.content + }) + return fields + } + _getCompiledLine() { + const compiler = this.definition._getCompilerObject() + const catchAllCellDelimiter = compiler[ParsersConstantsCompiler.catchAllCellDelimiter] + const str = compiler[ParsersConstantsCompiler.stringTemplate] + return str !== undefined ? Utils.formatStr(str, catchAllCellDelimiter, Object.assign(this._getFields(), this.cells)) : this.getLine() + } + get listDelimiter() { + return this.definition._getFromExtended(ParsersConstants.listDelimiter) + } + get contentKey() { + return this.definition._getFromExtended(ParsersConstants.contentKey) + } + get childrenKey() { + return this.definition._getFromExtended(ParsersConstants.childrenKey) + } + get childrenAreTextBlob() { + return this.definition._isBlobParser() + } + get isArrayElement() { + return this.definition._hasFromExtended(ParsersConstants.uniqueFirstWord) ? false : !this.definition.isSingle + } + get list() { + return this.listDelimiter ? this.content.split(this.listDelimiter) : super.list + } + get typedContent() { + // todo: probably a better way to do this, perhaps by defining a cellDelimiter at the particle level + // todo: this currently parse anything other than string types + if (this.listDelimiter) return this.content.split(this.listDelimiter) + const cells = this.parsedCells + if (cells.length === 2) return cells[1].parsed + return this.content + } + get typedTuple() { + const key = this.firstWord + if (this.childrenAreTextBlob) return [key, this.childrenToString()] + const { typedContent, contentKey, childrenKey } = this + if (contentKey || childrenKey) { + let obj = {} + if (childrenKey) obj[childrenKey] = this.childrenToString() + else obj = this.typedMap + if (contentKey) { + obj[contentKey] = typedContent + } + return [key, obj] + } + const hasChildren = this.length > 0 + const hasChildrenNoContent = typedContent === undefined && hasChildren + const shouldReturnValueAsObject = hasChildrenNoContent + if (shouldReturnValueAsObject) return [key, this.typedMap] + const hasChildrenAndContent = typedContent !== undefined && hasChildren + const shouldReturnValueAsContentPlusChildren = hasChildrenAndContent + // If the particle has a content and a subparticle return it as a string, as + // Javascript object values can't be both a leaf and a particle. + if (shouldReturnValueAsContentPlusChildren) return [key, this.contentWithChildren] + return [key, typedContent] + } + get _shouldSerialize() { + const should = this.shouldSerialize + return should === undefined ? true : should + } + get typedMap() { + const obj = {} + this.forEach(particle => { + if (!particle._shouldSerialize) return true + const tuple = particle.typedTuple + if (!particle.isArrayElement) obj[tuple[0]] = tuple[1] + else { + if (!obj[tuple[0]]) obj[tuple[0]] = [] + obj[tuple[0]].push(tuple[1]) + } + }) + return obj + } + fromTypedMap() {} + compile() { + if (this.isRoot()) return super.compile() + const def = this.definition + const indent = this._getCompiledIndentation() + const compiledLine = this._getCompiledLine() + if (def.isTerminalParser()) return indent + compiledLine + const compiler = def._getCompilerObject() + const openChildrenString = compiler[ParsersConstantsCompiler.openChildren] || "" + const closeChildrenString = compiler[ParsersConstantsCompiler.closeChildren] || "" + const childJoinCharacter = compiler[ParsersConstantsCompiler.joinChildrenWith] || "\n" + const compiledChildren = this.map(child => child.compile()).join(childJoinCharacter) + return `${indent + compiledLine}${openChildrenString} +${compiledChildren} +${indent}${closeChildrenString}` + } + // todo: remove + get cells() { + const cells = {} + this.parsedCells.forEach(cell => { + const cellTypeId = cell.cellTypeId + if (!cell.isCatchAll()) cells[cellTypeId] = cell.parsed + else { + if (!cells[cellTypeId]) cells[cellTypeId] = [] + cells[cellTypeId].push(cell.parsed) + } + }) + return cells + } +} +class BlobParser extends ParserBackedParticle { + createParserCombinator() { + return new Particle.ParserCombinator(BlobParser, {}) + } + getErrors() { + return [] + } +} +// todo: can we remove this? hard to extend. +class UnknownParserParticle extends ParserBackedParticle { + createParserCombinator() { + return new Particle.ParserCombinator(UnknownParserParticle, {}) + } + getErrors() { + return [new UnknownParserError(this)] + } +} +/* +A cell contains a word but also the type information for that word. +*/ +class AbstractParsersBackedCell { + constructor(particle, index, typeDef, cellTypeId, isCatchAll, parserDefinitionParser) { + this._typeDef = typeDef + this._particle = particle + this._isCatchAll = isCatchAll + this._index = index + this._cellTypeId = cellTypeId + this._parserDefinitionParser = parserDefinitionParser + } + getWord() { + return this._particle.getWord(this._index) + } + get definitionLineNumber() { + return this._typeDef.lineNumber + } + get cellTypeId() { + return this._cellTypeId + } + getParticle() { + return this._particle + } + get cellIndex() { + return this._index + } + isCatchAll() { + return this._isCatchAll + } + get min() { + return this.cellTypeDefinition.get(ParsersConstants.min) || "0" + } + get max() { + return this.cellTypeDefinition.get(ParsersConstants.max) || "100" + } + get placeholder() { + return this.cellTypeDefinition.get(ParsersConstants.examples) || "" + } + get paint() { + const definition = this.cellTypeDefinition + if (definition) return definition.paint // todo: why the undefined? + } + getAutoCompleteWords(partialWord = "") { + const cellDef = this.cellTypeDefinition + let words = cellDef ? cellDef._getAutocompleteWordOptions(this.getParticle().root) : [] + const runTimeOptions = this.getParticle().getRunTimeEnumOptions(this) + if (runTimeOptions) words = runTimeOptions.concat(words) + if (partialWord) words = words.filter(word => word.includes(partialWord)) + return words.map(word => { + return { + text: word, + displayText: word + } + }) + } + synthesizeCell(seed = Date.now()) { + // todo: cleanup + const cellDef = this.cellTypeDefinition + const enumOptions = cellDef._getFromExtended(ParsersConstants.enum) + if (enumOptions) return Utils.getRandomString(1, enumOptions.split(" ")) + return this._synthesizeCell(seed) + } + _getStumpEnumInput(crux) { + const cellDef = this.cellTypeDefinition + const enumOptions = cellDef._getFromExtended(ParsersConstants.enum) + if (!enumOptions) return undefined + const options = new Particle( + enumOptions + .split(" ") + .map(option => `option ${option}`) + .join("\n") + ) + return `select + name ${crux} +${options.toString(1)}` + } + _toStumpInput(crux) { + // todo: remove + const enumInput = this._getStumpEnumInput(crux) + if (enumInput) return enumInput + // todo: cleanup. We shouldn't have these dual cellType classes. + return `input + name ${crux} + placeholder ${this.placeholder}` + } + get cellTypeDefinition() { + return this._typeDef + } + _getErrorContext() { + return this.getParticle().getLine().split(" ")[0] // todo: WordBreakSymbol + } + isValid() { + const runTimeOptions = this.getParticle().getRunTimeEnumOptionsForValidation(this) + const word = this.getWord() + if (runTimeOptions) return runTimeOptions.includes(word) + return this.cellTypeDefinition.isValid(word, this.getParticle().root) && this._isValid() + } + getErrorIfAny() { + const word = this.getWord() + if (word !== undefined && this.isValid()) return undefined + // todo: refactor invalidwordError. We want better error messages. + return word === undefined || word === "" ? new MissingWordError(this) : new InvalidWordError(this) + } +} +AbstractParsersBackedCell.parserFunctionName = "" +class ParsersBitCell extends AbstractParsersBackedCell { + _isValid() { + const word = this.getWord() + return word === "0" || word === "1" + } + _synthesizeCell() { + return Utils.getRandomString(1, "01".split("")) + } + get regexString() { + return "[01]" + } + get parsed() { + const word = this.getWord() + return !!parseInt(word) + } +} +ParsersBitCell.defaultPaint = "constant.numeric" +class ParsersNumericCell extends AbstractParsersBackedCell { + _toStumpInput(crux) { + return `input + name ${crux} + type number + placeholder ${this.placeholder} + min ${this.min} + max ${this.max}` + } +} +class ParsersIntCell extends ParsersNumericCell { + _isValid() { + const word = this.getWord() + const num = parseInt(word) + if (isNaN(num)) return false + return num.toString() === word + } + _synthesizeCell(seed) { + return Utils.randomUniformInt(parseInt(this.min), parseInt(this.max), seed).toString() + } + get regexString() { + return "-?[0-9]+" + } + get parsed() { + const word = this.getWord() + return parseInt(word) + } +} +ParsersIntCell.defaultPaint = "constant.numeric.integer" +ParsersIntCell.parserFunctionName = "parseInt" +class ParsersFloatCell extends ParsersNumericCell { + _isValid() { + const word = this.getWord() + const num = parseFloat(word) + return !isNaN(num) && /^-?\d*(\.\d+)?([eE][+-]?\d+)?$/.test(word) + } + _synthesizeCell(seed) { + return Utils.randomUniformFloat(parseFloat(this.min), parseFloat(this.max), seed).toString() + } + get regexString() { + return "-?d*(.d+)?" + } + get parsed() { + const word = this.getWord() + return parseFloat(word) + } +} +ParsersFloatCell.defaultPaint = "constant.numeric.float" +ParsersFloatCell.parserFunctionName = "parseFloat" +// ErrorCellType => parsers asks for a '' cell type here but the parsers does not specify a '' cell type. (todo: bring in didyoumean?) +class ParsersBoolCell extends AbstractParsersBackedCell { + constructor() { + super(...arguments) + this._trues = new Set(["1", "true", "t", "yes"]) + this._falses = new Set(["0", "false", "f", "no"]) + } + _isValid() { + const word = this.getWord() + const str = word.toLowerCase() + return this._trues.has(str) || this._falses.has(str) + } + _synthesizeCell() { + return Utils.getRandomString(1, ["1", "true", "t", "yes", "0", "false", "f", "no"]) + } + _getOptions() { + return Array.from(this._trues).concat(Array.from(this._falses)) + } + get regexString() { + return "(?:" + this._getOptions().join("|") + ")" + } + get parsed() { + const word = this.getWord() + return this._trues.has(word.toLowerCase()) + } +} +ParsersBoolCell.defaultPaint = "constant.numeric" +class ParsersAnyCell extends AbstractParsersBackedCell { + _isValid() { + return true + } + _synthesizeCell() { + const examples = this.cellTypeDefinition._getFromExtended(ParsersConstants.examples) + if (examples) return Utils.getRandomString(1, examples.split(" ")) + return this._parserDefinitionParser.parserIdFromDefinition + "-" + this.constructor.name + } + get regexString() { + return "[^ ]+" + } + get parsed() { + return this.getWord() + } +} +class ParsersKeywordCell extends ParsersAnyCell { + _synthesizeCell() { + return this._parserDefinitionParser.cruxIfAny + } +} +ParsersKeywordCell.defaultPaint = "keyword" +class ParsersExtraWordCellTypeCell extends AbstractParsersBackedCell { + _isValid() { + return false + } + synthesizeCell() { + throw new Error(`Trying to synthesize a ParsersExtraWordCellTypeCell`) + return this._synthesizeCell() + } + _synthesizeCell() { + return "extraWord" // should never occur? + } + get parsed() { + return this.getWord() + } + getErrorIfAny() { + return new ExtraWordError(this) + } +} +class ParsersUnknownCellTypeCell extends AbstractParsersBackedCell { + _isValid() { + return false + } + synthesizeCell() { + throw new Error(`Trying to synthesize an ParsersUnknownCellTypeCell`) + return this._synthesizeCell() + } + _synthesizeCell() { + return "extraWord" // should never occur? + } + get parsed() { + return this.getWord() + } + getErrorIfAny() { + return new UnknownCellTypeError(this) + } +} +class AbstractParticleError { + constructor(particle) { + this._particle = particle + } + getLineIndex() { + return this.lineNumber - 1 + } + get lineNumber() { + return this.getParticle()._getLineNumber() // todo: handle sourcemaps + } + isCursorOnWord(lineIndex, characterIndex) { + return lineIndex === this.getLineIndex() && this._doesCharacterIndexFallOnWord(characterIndex) + } + _doesCharacterIndexFallOnWord(characterIndex) { + return this.cellIndex === this.getParticle().getWordIndexAtCharacterIndex(characterIndex) + } + // convenience method. may be removed. + isBlankLineError() { + return false + } + // convenience method. may be removed. + isMissingWordError() { + return false + } + getIndent() { + return this.getParticle().indentation + } + getCodeMirrorLineWidgetElement(onApplySuggestionCallBack = () => {}) { + const suggestion = this.suggestionMessage + if (this.isMissingWordError()) return this._getCodeMirrorLineWidgetElementCellTypeHints() + if (suggestion) return this._getCodeMirrorLineWidgetElementWithSuggestion(onApplySuggestionCallBack, suggestion) + return this._getCodeMirrorLineWidgetElementWithoutSuggestion() + } + get parserId() { + return this.getParticle().definition.parserIdFromDefinition + } + _getCodeMirrorLineWidgetElementCellTypeHints() { + const el = document.createElement("div") + el.appendChild(document.createTextNode(this.getIndent() + this.getParticle().definition.lineHints)) + el.className = "LintCellTypeHints" + return el + } + _getCodeMirrorLineWidgetElementWithoutSuggestion() { + const el = document.createElement("div") + el.appendChild(document.createTextNode(this.getIndent() + this.message)) + el.className = "LintError" + return el + } + _getCodeMirrorLineWidgetElementWithSuggestion(onApplySuggestionCallBack, suggestion) { + const el = document.createElement("div") + el.appendChild(document.createTextNode(this.getIndent() + `${this.errorTypeName}. Suggestion: ${suggestion}`)) + el.className = "LintErrorWithSuggestion" + el.onclick = () => { + this.applySuggestion() + onApplySuggestionCallBack() + } + return el + } + getLine() { + return this.getParticle().getLine() + } + getExtension() { + return this.getParticle().handParsersProgram.extensionName + } + getParticle() { + return this._particle + } + get errorTypeName() { + return this.constructor.name.replace("Error", "") + } + get cellIndex() { + return 0 + } + toObject() { + return { + type: this.errorTypeName, + line: this.lineNumber, + cell: this.cellIndex, + suggestion: this.suggestionMessage, + path: this.getParticle().getFirstWordPath(), + message: this.message + } + } + hasSuggestion() { + return this.suggestionMessage !== "" + } + get suggestionMessage() { + return "" + } + toString() { + return this.message + } + applySuggestion() {} + get message() { + return `${this.errorTypeName} at line ${this.lineNumber} cell ${this.cellIndex}.` + } +} +class AbstractCellError extends AbstractParticleError { + constructor(cell) { + super(cell.getParticle()) + this._cell = cell + } + get cell() { + return this._cell + } + get cellIndex() { + return this._cell.cellIndex + } + get wordSuggestion() { + return Utils.didYouMean( + this.cell.getWord(), + this.cell.getAutoCompleteWords().map(option => option.text) + ) + } +} +class UnknownParserError extends AbstractParticleError { + get message() { + const particle = this.getParticle() + const parentParticle = particle.parent + const options = parentParticle._getParser().getFirstWordOptions() + return super.message + ` Invalid parser "${particle.firstWord}". Valid parsers are: ${Utils._listToEnglishText(options, 7)}.` + } + get wordSuggestion() { + const particle = this.getParticle() + const parentParticle = particle.parent + return Utils.didYouMean( + particle.firstWord, + parentParticle.getAutocompleteResults("", 0).map(option => option.text) + ) + } + get suggestionMessage() { + const suggestion = this.wordSuggestion + const particle = this.getParticle() + if (suggestion) return `Change "${particle.firstWord}" to "${suggestion}"` + return "" + } + applySuggestion() { + const suggestion = this.wordSuggestion + if (suggestion) this.getParticle().setWord(this.cellIndex, suggestion) + return this + } +} +class ParserDefinedError extends AbstractParticleError { + constructor(particle, message) { + super() + this._particle = particle + this._message = message + } + get message() { + return this._message + } +} +class BlankLineError extends UnknownParserError { + get message() { + return super.message + ` Line: "${this.getParticle().getLine()}". Blank lines are errors.` + } + // convenience method + isBlankLineError() { + return true + } + get suggestionMessage() { + return `Delete line ${this.lineNumber}` + } + applySuggestion() { + this.getParticle().destroy() + return this + } +} +class MissingRequiredParserError extends AbstractParticleError { + constructor(particle, missingParserId) { + super(particle) + this._missingParserId = missingParserId + } + get message() { + return super.message + ` A "${this._missingParserId}" is required.` + } +} +class ParserUsedMultipleTimesError extends AbstractParticleError { + get message() { + return super.message + ` Multiple "${this.getParticle().firstWord}" found.` + } + get suggestionMessage() { + return `Delete line ${this.lineNumber}` + } + applySuggestion() { + return this.getParticle().destroy() + } +} +class LineAppearsMultipleTimesError extends AbstractParticleError { + get message() { + return super.message + ` "${this.getParticle().getLine()}" appears multiple times.` + } + get suggestionMessage() { + return `Delete line ${this.lineNumber}` + } + applySuggestion() { + return this.getParticle().destroy() + } +} +class UnknownCellTypeError extends AbstractCellError { + get message() { + return super.message + ` No cellType "${this.cell.cellTypeId}" found. Language parsers for "${this.getExtension()}" may need to be fixed.` + } +} +class InvalidWordError extends AbstractCellError { + get message() { + return super.message + ` "${this.cell.getWord()}" does not fit in cellType "${this.cell.cellTypeId}".` + } + get suggestionMessage() { + const suggestion = this.wordSuggestion + if (suggestion) return `Change "${this.cell.getWord()}" to "${suggestion}"` + return "" + } + applySuggestion() { + const suggestion = this.wordSuggestion + if (suggestion) this.getParticle().setWord(this.cellIndex, suggestion) + return this + } +} +class ExtraWordError extends AbstractCellError { + get message() { + return super.message + ` Extra word "${this.cell.getWord()}" in ${this.parserId}.` + } + get suggestionMessage() { + return `Delete word "${this.cell.getWord()}" at cell ${this.cellIndex}` + } + applySuggestion() { + return this.getParticle().deleteWordAt(this.cellIndex) + } +} +class MissingWordError extends AbstractCellError { + // todo: autocomplete suggestion + get message() { + return super.message + ` Missing word for cell "${this.cell.cellTypeId}".` + } + isMissingWordError() { + return true + } +} +// todo: add standard types, enum types, from disk types +class AbstractParsersWordTestParser extends Particle {} +class ParsersRegexTestParser extends AbstractParsersWordTestParser { + isValid(str) { + if (!this._regex) this._regex = new RegExp("^" + this.content + "$") + return !!str.match(this._regex) + } +} +class ParsersReservedWordsTestParser extends AbstractParsersWordTestParser { + isValid(str) { + if (!this._set) this._set = new Set(this.content.split(" ")) + return !this._set.has(str) + } +} +// todo: remove in favor of custom word type constructors +class EnumFromCellTypesTestParser extends AbstractParsersWordTestParser { + _getEnumFromCellTypes(programRootParticle) { + const cellTypeIds = this.getWordsFrom(1) + const enumGroup = cellTypeIds.join(" ") + // note: hack where we store it on the program. otherwise has global effects. + if (!programRootParticle._enumMaps) programRootParticle._enumMaps = {} + if (programRootParticle._enumMaps[enumGroup]) return programRootParticle._enumMaps[enumGroup] + const wordIndex = 1 + const map = {} + const cellTypeMap = {} + cellTypeIds.forEach(typeId => (cellTypeMap[typeId] = true)) + programRootParticle.allTypedWords + .filter(typedWord => cellTypeMap[typedWord.type]) + .forEach(typedWord => { + map[typedWord.word] = true + }) + programRootParticle._enumMaps[enumGroup] = map + return map + } + // todo: remove + isValid(str, programRootParticle) { + return this._getEnumFromCellTypes(programRootParticle)[str] === true + } +} +class ParsersEnumTestParticle extends AbstractParsersWordTestParser { + isValid(str) { + // enum c c++ java + return !!this.getOptions()[str] + } + getOptions() { + if (!this._map) this._map = Utils.arrayToMap(this.getWordsFrom(1)) + return this._map + } +} +class cellTypeDefinitionParser extends AbstractExtendibleParticle { + createParserCombinator() { + const types = {} + types[ParsersConstants.regex] = ParsersRegexTestParser + types[ParsersConstants.reservedWords] = ParsersReservedWordsTestParser + types[ParsersConstants.enumFromCellTypes] = EnumFromCellTypesTestParser + types[ParsersConstants.enum] = ParsersEnumTestParticle + types[ParsersConstants.paint] = Particle + types[ParsersConstants.comment] = Particle + types[ParsersConstants.examples] = Particle + types[ParsersConstants.min] = Particle + types[ParsersConstants.max] = Particle + types[ParsersConstants.description] = Particle + types[ParsersConstants.extends] = Particle + return new Particle.ParserCombinator(undefined, types) + } + get id() { + return this.getWord(0) + } + get idToParticleMap() { + return this.parent.cellTypeDefinitions + } + getGetter(wordIndex) { + const wordToNativeJavascriptTypeParser = this.getCellConstructor().parserFunctionName + return `get ${this.cellTypeId}() { + return ${wordToNativeJavascriptTypeParser ? wordToNativeJavascriptTypeParser + `(this.getWord(${wordIndex}))` : `this.getWord(${wordIndex})`} + }` + } + getCatchAllGetter(wordIndex) { + const wordToNativeJavascriptTypeParser = this.getCellConstructor().parserFunctionName + return `get ${this.cellTypeId}() { + return ${wordToNativeJavascriptTypeParser ? `this.getWordsFrom(${wordIndex}).map(val => ${wordToNativeJavascriptTypeParser}(val))` : `this.getWordsFrom(${wordIndex})`} + }` + } + // `this.getWordsFrom(${requireds.length + 1})` + // todo: cleanup typings. todo: remove this hidden logic. have a "baseType" property? + getCellConstructor() { + return this.preludeKind || ParsersAnyCell + } + get preludeKind() { + return PreludeKinds[this.getWord(0)] || PreludeKinds[this._getExtendedCellTypeId()] + } + get preludeKindId() { + if (PreludeKinds[this.getWord(0)]) return this.getWord(0) + else if (PreludeKinds[this._getExtendedCellTypeId()]) return this._getExtendedCellTypeId() + return PreludeCellTypeIds.anyCell + } + _getExtendedCellTypeId() { + const arr = this._getAncestorsArray() + return arr[arr.length - 1].id + } + get paint() { + const hs = this._getFromExtended(ParsersConstants.paint) + if (hs) return hs + const preludeKind = this.preludeKind + if (preludeKind) return preludeKind.defaultPaint + } + _getEnumOptions() { + const enumParticle = this._getParticleFromExtended(ParsersConstants.enum) + if (!enumParticle) return undefined + // we sort by longest first to capture longest match first. todo: add test + const options = Object.keys(enumParticle.getParticle(ParsersConstants.enum).getOptions()) + options.sort((a, b) => b.length - a.length) + return options + } + _getEnumFromCellTypeOptions(program) { + const particle = this._getParticleFromExtended(ParsersConstants.enumFromCellTypes) + return particle ? Object.keys(particle.getParticle(ParsersConstants.enumFromCellTypes)._getEnumFromCellTypes(program)) : undefined + } + _getAutocompleteWordOptions(program) { + return this._getEnumOptions() || this._getEnumFromCellTypeOptions(program) || [] + } + get regexString() { + // todo: enum + const enumOptions = this._getEnumOptions() + return this._getFromExtended(ParsersConstants.regex) || (enumOptions ? "(?:" + enumOptions.join("|") + ")" : "[^ ]*") + } + _getAllTests() { + return this._getChildrenByParserInExtended(AbstractParsersWordTestParser) + } + isValid(str, programRootParticle) { + return this._getAllTests().every(particle => particle.isValid(str, programRootParticle)) + } + get cellTypeId() { + return this.getWord(0) + } +} +class AbstractCellParser { + constructor(definition) { + this._definition = definition + } + get catchAllCellTypeId() { + return this._definition._getFromExtended(ParsersConstants.catchAllCellType) + } + // todo: improve layout (use bold?) + get lineHints() { + const catchAllCellTypeId = this.catchAllCellTypeId + const parserId = this._definition.cruxIfAny || this._definition.id // todo: cleanup + return `${parserId}: ${this.getRequiredCellTypeIds().join(" ")}${catchAllCellTypeId ? ` ${catchAllCellTypeId}...` : ""}` + } + getRequiredCellTypeIds() { + if (!this._requiredCellTypeIds) { + const parameters = this._definition._getFromExtended(ParsersConstants.cells) + this._requiredCellTypeIds = parameters ? parameters.split(" ") : [] + } + return this._requiredCellTypeIds + } + _getCellTypeId(cellIndex, requiredCellTypeIds, totalWordCount) { + return requiredCellTypeIds[cellIndex] + } + _isCatchAllCell(cellIndex, numberOfRequiredCells, totalWordCount) { + return cellIndex >= numberOfRequiredCells + } + getCellArray(particle = undefined) { + const wordCount = particle ? particle.words.length : 0 + const def = this._definition + const parsersProgram = def.languageDefinitionProgram + const requiredCellTypeIds = this.getRequiredCellTypeIds() + const numberOfRequiredCells = requiredCellTypeIds.length + const actualWordCountOrRequiredCellCount = Math.max(wordCount, numberOfRequiredCells) + const cells = [] + // A for loop instead of map because "numberOfCellsToFill" can be longer than words.length + for (let cellIndex = 0; cellIndex < actualWordCountOrRequiredCellCount; cellIndex++) { + const isCatchAll = this._isCatchAllCell(cellIndex, numberOfRequiredCells, wordCount) + let cellTypeId = isCatchAll ? this.catchAllCellTypeId : this._getCellTypeId(cellIndex, requiredCellTypeIds, wordCount) + let cellTypeDefinition = parsersProgram.getCellTypeDefinitionById(cellTypeId) + let cellConstructor + if (cellTypeDefinition) cellConstructor = cellTypeDefinition.getCellConstructor() + else if (cellTypeId) cellConstructor = ParsersUnknownCellTypeCell + else { + cellConstructor = ParsersExtraWordCellTypeCell + cellTypeId = PreludeCellTypeIds.extraWordCell + cellTypeDefinition = parsersProgram.getCellTypeDefinitionById(cellTypeId) + } + const anyCellConstructor = cellConstructor + cells[cellIndex] = new anyCellConstructor(particle, cellIndex, cellTypeDefinition, cellTypeId, isCatchAll, def) + } + return cells + } +} +class PrefixCellParser extends AbstractCellParser {} +class PostfixCellParser extends AbstractCellParser { + _isCatchAllCell(cellIndex, numberOfRequiredCells, totalWordCount) { + return cellIndex < totalWordCount - numberOfRequiredCells + } + _getCellTypeId(cellIndex, requiredCellTypeIds, totalWordCount) { + const catchAllWordCount = Math.max(totalWordCount - requiredCellTypeIds.length, 0) + return requiredCellTypeIds[cellIndex - catchAllWordCount] + } +} +class OmnifixCellParser extends AbstractCellParser { + getCellArray(particle = undefined) { + const cells = [] + const def = this._definition + const program = particle ? particle.root : undefined + const parsersProgram = def.languageDefinitionProgram + const words = particle ? particle.words : [] + const requiredCellTypeDefs = this.getRequiredCellTypeIds().map(cellTypeId => parsersProgram.getCellTypeDefinitionById(cellTypeId)) + const catchAllCellTypeId = this.catchAllCellTypeId + const catchAllCellTypeDef = catchAllCellTypeId && parsersProgram.getCellTypeDefinitionById(catchAllCellTypeId) + words.forEach((word, wordIndex) => { + let cellConstructor + for (let index = 0; index < requiredCellTypeDefs.length; index++) { + const cellTypeDefinition = requiredCellTypeDefs[index] + if (cellTypeDefinition.isValid(word, program)) { + // todo: cleanup cellIndex/wordIndex stuff + cellConstructor = cellTypeDefinition.getCellConstructor() + cells.push(new cellConstructor(particle, wordIndex, cellTypeDefinition, cellTypeDefinition.id, false, def)) + requiredCellTypeDefs.splice(index, 1) + return true + } + } + if (catchAllCellTypeDef && catchAllCellTypeDef.isValid(word, program)) { + cellConstructor = catchAllCellTypeDef.getCellConstructor() + cells.push(new cellConstructor(particle, wordIndex, catchAllCellTypeDef, catchAllCellTypeId, true, def)) + return true + } + cells.push(new ParsersUnknownCellTypeCell(particle, wordIndex, undefined, undefined, false, def)) + }) + const wordCount = words.length + requiredCellTypeDefs.forEach((cellTypeDef, index) => { + let cellConstructor = cellTypeDef.getCellConstructor() + cells.push(new cellConstructor(particle, wordCount + index, cellTypeDef, cellTypeDef.id, false, def)) + }) + return cells + } +} +class ParsersExampleParser extends Particle {} +class ParsersCompilerParser extends Particle { + createParserCombinator() { + const types = [ + ParsersConstantsCompiler.stringTemplate, + ParsersConstantsCompiler.indentCharacter, + ParsersConstantsCompiler.catchAllCellDelimiter, + ParsersConstantsCompiler.joinChildrenWith, + ParsersConstantsCompiler.openChildren, + ParsersConstantsCompiler.closeChildren + ] + const map = {} + types.forEach(type => { + map[type] = Particle + }) + return new Particle.ParserCombinator(undefined, map) + } +} +class AbstractParserConstantParser extends Particle { + constructor(children, line, parent) { + super(children, line, parent) + parent[this.identifier] = this.constantValue + } + getGetter() { + return `get ${this.identifier}() { return ${this.constantValueAsJsText} }` + } + get identifier() { + return this.getWord(1) + } + get constantValueAsJsText() { + const words = this.getWordsFrom(2) + return words.length > 1 ? `[${words.join(",")}]` : words[0] + } + get constantValue() { + return JSON.parse(this.constantValueAsJsText) + } +} +class ParsersParserConstantInt extends AbstractParserConstantParser {} +class ParsersParserConstantString extends AbstractParserConstantParser { + get constantValueAsJsText() { + return "`" + Utils.escapeBackTicks(this.constantValue) + "`" + } + get constantValue() { + return this.length ? this.childrenToString() : this.getWordsFrom(2).join(" ") + } +} +class ParsersParserConstantFloat extends AbstractParserConstantParser {} +class ParsersParserConstantBoolean extends AbstractParserConstantParser {} +class AbstractParserDefinitionParser extends AbstractExtendibleParticle { + createParserCombinator() { + // todo: some of these should just be on nonRootParticles + const types = [ + ParsersConstants.popularity, + ParsersConstants.inScope, + ParsersConstants.cells, + ParsersConstants.extends, + ParsersConstants.description, + ParsersConstants.catchAllParser, + ParsersConstants.catchAllCellType, + ParsersConstants.cellParser, + ParsersConstants.extensions, + ParsersConstants.tags, + ParsersConstants.crux, + ParsersConstants.cruxFromId, + ParsersConstants.listDelimiter, + ParsersConstants.contentKey, + ParsersConstants.childrenKey, + ParsersConstants.uniqueFirstWord, + ParsersConstants.uniqueLine, + ParsersConstants.pattern, + ParsersConstants.baseParser, + ParsersConstants.required, + ParsersConstants.root, + ParsersConstants._rootNodeJsHeader, + ParsersConstants.javascript, + ParsersConstants.compilesTo, + ParsersConstants.javascript, + ParsersConstants.single, + ParsersConstants.comment + ] + const map = {} + types.forEach(type => { + map[type] = Particle + }) + map[ParsersConstantsConstantTypes.boolean] = ParsersParserConstantBoolean + map[ParsersConstantsConstantTypes.int] = ParsersParserConstantInt + map[ParsersConstantsConstantTypes.string] = ParsersParserConstantString + map[ParsersConstantsConstantTypes.float] = ParsersParserConstantFloat + map[ParsersConstants.compilerParser] = ParsersCompilerParser + map[ParsersConstants.example] = ParsersExampleParser + return new Particle.ParserCombinator(undefined, map, [{ regex: HandParsersProgram.parserFullRegex, parser: parserDefinitionParser }]) + } + toTypeScriptInterface(used = new Set()) { + let childrenInterfaces = [] + let properties = [] + const inScope = this.firstWordMapWithDefinitions + const thisId = this.id + used.add(thisId) + Object.keys(inScope).forEach(key => { + const def = inScope[key] + const map = def.firstWordMapWithDefinitions + const id = def.id + const optionalTag = def.isRequired() ? "" : "?" + const escapedKey = key.match(/\?/) ? `"${key}"` : key + const description = def.description + if (Object.keys(map).length && !used.has(id)) { + childrenInterfaces.push(def.toTypeScriptInterface(used)) + properties.push(` ${escapedKey}${optionalTag}: ${id}`) + } else properties.push(` ${escapedKey}${optionalTag}: any${description ? " // " + description : ""}`) + }) + properties.sort() + const description = this.description + const myInterface = "" + return `${childrenInterfaces.join("\n")} +${description ? "// " + description : ""} +interface ${thisId} { +${properties.join("\n")} +}`.trim() + } + get id() { + return this.getWord(0) + } + get idWithoutSuffix() { + return this.id.replace(HandParsersProgram.parserSuffixRegex, "") + } + get constantsObject() { + const obj = this._getUniqueConstantParticles() + Object.keys(obj).forEach(key => (obj[key] = obj[key].constantValue)) + return obj + } + _getUniqueConstantParticles(extended = true) { + const obj = {} + const items = extended ? this._getChildrenByParserInExtended(AbstractParserConstantParser) : this.getChildrenByParser(AbstractParserConstantParser) + items.reverse() // Last definition wins. + items.forEach(particle => (obj[particle.identifier] = particle)) + return obj + } + get examples() { + return this._getChildrenByParserInExtended(ParsersExampleParser) + } + get parserIdFromDefinition() { + return this.getWord(0) + } + // todo: remove? just reused parserId + get generatedClassName() { + return this.parserIdFromDefinition + } + _hasValidParserId() { + return !!this.generatedClassName + } + _isAbstract() { + return this.id.startsWith(ParsersConstants.abstractParserPrefix) + } + get cruxIfAny() { + return this.get(ParsersConstants.crux) || (this._hasFromExtended(ParsersConstants.cruxFromId) ? this.idWithoutSuffix : undefined) + } + get regexMatch() { + return this.get(ParsersConstants.pattern) + } + get firstCellEnumOptions() { + const firstCellDef = this._getMyCellTypeDefs()[0] + return firstCellDef ? firstCellDef._getEnumOptions() : undefined + } + get languageDefinitionProgram() { + return this.root + } + get customJavascriptMethods() { + const hasJsCode = this.has(ParsersConstants.javascript) + return hasJsCode ? this.getParticle(ParsersConstants.javascript).childrenToString() : "" + } + get firstWordMapWithDefinitions() { + if (!this._cache_firstWordToParticleDefMap) this._cache_firstWordToParticleDefMap = this._createParserInfo(this._getInScopeParserIds()).firstWordMap + return this._cache_firstWordToParticleDefMap + } + // todo: remove + get runTimeFirstWordsInScope() { + return this._getParser().getFirstWordOptions() + } + _getMyCellTypeDefs() { + const requiredCells = this.get(ParsersConstants.cells) + if (!requiredCells) return [] + const parsersProgram = this.languageDefinitionProgram + return requiredCells.split(" ").map(cellTypeId => { + const cellTypeDef = parsersProgram.getCellTypeDefinitionById(cellTypeId) + if (!cellTypeDef) throw new Error(`No cellType "${cellTypeId}" found`) + return cellTypeDef + }) + } + // todo: what happens when you have a cell getter and constant with same name? + get cellGettersAndParserConstants() { + // todo: add cellType parsings + const parsersProgram = this.languageDefinitionProgram + const getters = this._getMyCellTypeDefs().map((cellTypeDef, index) => cellTypeDef.getGetter(index)) + const catchAllCellTypeId = this.get(ParsersConstants.catchAllCellType) + if (catchAllCellTypeId) getters.push(parsersProgram.getCellTypeDefinitionById(catchAllCellTypeId).getCatchAllGetter(getters.length)) + // Constants + Object.values(this._getUniqueConstantParticles(false)).forEach(particle => getters.push(particle.getGetter())) + return getters.join("\n") + } + _createParserInfo(parserIdsInScope) { + const result = { + firstWordMap: {}, + regexTests: [] + } + if (!parserIdsInScope.length) return result + const allProgramParserDefinitionsMap = this.programParserDefinitionCache + Object.keys(allProgramParserDefinitionsMap) + .filter(parserId => { + const def = allProgramParserDefinitionsMap[parserId] + return def.isOrExtendsAParserInScope(parserIdsInScope) && !def._isAbstract() + }) + .forEach(parserId => { + const def = allProgramParserDefinitionsMap[parserId] + const regex = def.regexMatch + const crux = def.cruxIfAny + const enumOptions = def.firstCellEnumOptions + if (regex) result.regexTests.push({ regex: regex, parser: def.parserIdFromDefinition }) + else if (crux) result.firstWordMap[crux] = def + else if (enumOptions) { + enumOptions.forEach(option => (result.firstWordMap[option] = def)) + } + }) + return result + } + get topParserDefinitions() { + const arr = Object.values(this.firstWordMapWithDefinitions) + arr.sort(Utils.makeSortByFn(definition => definition.popularity)) + arr.reverse() + return arr + } + _getMyInScopeParserIds(target = this) { + const parsersParticle = target.getParticle(ParsersConstants.inScope) + const scopedDefinitionIds = target.myScopedParserDefinitions.map(def => def.id) + return parsersParticle ? parsersParticle.getWordsFrom(1).concat(scopedDefinitionIds) : scopedDefinitionIds + } + _getInScopeParserIds() { + // todo: allow multiple of these if we allow mixins? + const ids = this._getMyInScopeParserIds() + const parentDef = this._getExtendedParent() + return parentDef ? ids.concat(parentDef._getInScopeParserIds()) : ids + } + get isSingle() { + const hit = this._getParticleFromExtended(ParsersConstants.single) + return hit && hit.get(ParsersConstants.single) !== "false" + } + get isUniqueLine() { + const hit = this._getParticleFromExtended(ParsersConstants.uniqueLine) + return hit && hit.get(ParsersConstants.uniqueLine) !== "false" + } + isRequired() { + return this._hasFromExtended(ParsersConstants.required) + } + getParserDefinitionByParserId(parserId) { + // todo: return catch all? + const def = this.programParserDefinitionCache[parserId] + if (def) return def + this.languageDefinitionProgram._addDefaultCatchAllBlobParser() // todo: cleanup. Why did I do this? Needs to be removed or documented. + const particleDef = this.languageDefinitionProgram.programParserDefinitionCache[parserId] + if (!particleDef) throw new Error(`No definition found for parser id "${parserId}". Particle: \n---\n${this.asString}\n---`) + return particleDef + } + isDefined(parserId) { + return !!this.programParserDefinitionCache[parserId] + } + get idToParticleMap() { + return this.programParserDefinitionCache + } + _amIRoot() { + if (this._cache_isRoot === undefined) this._cache_isRoot = this._languageRootParticle === this + return this._cache_isRoot + } + get _languageRootParticle() { + return this.root.rootParserDefinition + } + _isErrorParser() { + return this.get(ParsersConstants.baseParser) === ParsersConstants.errorParser + } + _isBlobParser() { + // Do not check extended classes. Only do once. + return this._getFromExtended(ParsersConstants.baseParser) === ParsersConstants.blobParser + } + get errorMethodToJavascript() { + if (this._isBlobParser()) return "getErrors() { return [] }" // Skips parsing child particles for perf gains. + if (this._isErrorParser()) return "getErrors() { return this._getErrorParserErrors() }" + return "" + } + get parserAsJavascript() { + if (this._isBlobParser()) + // todo: do we need this? + return "createParserCombinator() { return new Particle.ParserCombinator(this._getBlobParserCatchAllParser())}" + const parserInfo = this._createParserInfo(this._getMyInScopeParserIds()) + const myFirstWordMap = parserInfo.firstWordMap + const regexRules = parserInfo.regexTests + // todo: use constants in first word maps? + // todo: cache the super extending? + const firstWords = Object.keys(myFirstWordMap) + const hasFirstWords = firstWords.length + const catchAllParser = this.catchAllParserToJavascript + if (!hasFirstWords && !catchAllParser && !regexRules.length) return "" + const firstWordsStr = hasFirstWords + ? `Object.assign(Object.assign({}, super.createParserCombinator()._getFirstWordMapAsObject()), {` + firstWords.map(firstWord => `"${firstWord}" : ${myFirstWordMap[firstWord].parserIdFromDefinition}`).join(",\n") + "})" + : "undefined" + const regexStr = regexRules.length + ? `[${regexRules + .map(rule => { + return `{regex: /${rule.regex}/, parser: ${rule.parser}}` + }) + .join(",")}]` + : "undefined" + const catchAllStr = catchAllParser ? catchAllParser : this._amIRoot() ? `this._getBlobParserCatchAllParser()` : "undefined" + const scopedParserJavascript = this.myScopedParserDefinitions.map(def => def.asJavascriptClass).join("\n\n") + return `createParserCombinator() {${scopedParserJavascript} + return new Particle.ParserCombinator(${catchAllStr}, ${firstWordsStr}, ${regexStr}) + }` + } + get myScopedParserDefinitions() { + return this.getChildrenByParser(parserDefinitionParser) + } + get catchAllParserToJavascript() { + if (this._isBlobParser()) return "this._getBlobParserCatchAllParser()" + const parserId = this.get(ParsersConstants.catchAllParser) + if (!parserId) return "" + const particleDef = this.getParserDefinitionByParserId(parserId) + return particleDef.generatedClassName + } + get asJavascriptClass() { + const components = [this.parserAsJavascript, this.errorMethodToJavascript, this.cellGettersAndParserConstants, this.customJavascriptMethods].filter(identity => identity) + const thisClassName = this.generatedClassName + if (this._amIRoot()) { + components.push(`static cachedHandParsersProgramRoot = new HandParsersProgram(\`${Utils.escapeBackTicks(this.parent.toString().replace(/\\/g, "\\\\"))}\`) + get handParsersProgram() { + return this.constructor.cachedHandParsersProgramRoot + }`) + components.push(`static rootParser = ${thisClassName}`) + } + return `class ${thisClassName} extends ${this._getExtendsClassName()} { + ${components.join("\n")} + }` + } + _getExtendsClassName() { + const extendedDef = this._getExtendedParent() + return extendedDef ? extendedDef.generatedClassName : "ParserBackedParticle" + } + _getCompilerObject() { + let obj = {} + const items = this._getChildrenByParserInExtended(ParsersCompilerParser) + items.reverse() // Last definition wins. + items.forEach(particle => { + obj = Object.assign(obj, particle.toObject()) // todo: what about multiline strings? + }) + return obj + } + // todo: improve layout (use bold?) + get lineHints() { + return this.cellParser.lineHints + } + isOrExtendsAParserInScope(firstWordsInScope) { + const chain = this._getParserInheritanceSet() + return firstWordsInScope.some(firstWord => chain.has(firstWord)) + } + isTerminalParser() { + return !this._getFromExtended(ParsersConstants.inScope) && !this._getFromExtended(ParsersConstants.catchAllParser) + } + get sublimeMatchLine() { + const regexMatch = this.regexMatch + if (regexMatch) return `'${regexMatch}'` + const cruxMatch = this.cruxIfAny + if (cruxMatch) return `'^ *${Utils.escapeRegExp(cruxMatch)}(?: |$)'` + const enumOptions = this.firstCellEnumOptions + if (enumOptions) return `'^ *(${Utils.escapeRegExp(enumOptions.join("|"))})(?: |$)'` + } + // todo: refactor. move some parts to cellParser? + _toSublimeMatchBlock() { + const defaultPaint = "source" + const program = this.languageDefinitionProgram + const cellParser = this.cellParser + const requiredCellTypeIds = cellParser.getRequiredCellTypeIds() + const catchAllCellTypeId = cellParser.catchAllCellTypeId + const firstCellTypeDef = program.getCellTypeDefinitionById(requiredCellTypeIds[0]) + const firstWordPaint = (firstCellTypeDef ? firstCellTypeDef.paint : defaultPaint) + "." + this.parserIdFromDefinition + const topHalf = ` '${this.parserIdFromDefinition}': + - match: ${this.sublimeMatchLine} + scope: ${firstWordPaint}` + if (catchAllCellTypeId) requiredCellTypeIds.push(catchAllCellTypeId) + if (!requiredCellTypeIds.length) return topHalf + const captures = requiredCellTypeIds + .map((cellTypeId, index) => { + const cellTypeDefinition = program.getCellTypeDefinitionById(cellTypeId) // todo: cleanup + if (!cellTypeDefinition) throw new Error(`No ${ParsersConstants.cellType} ${cellTypeId} found`) // todo: standardize error/capture error at parsers time + return ` ${index + 1}: ${(cellTypeDefinition.paint || defaultPaint) + "." + cellTypeDefinition.cellTypeId}` + }) + .join("\n") + const cellTypesToRegex = cellTypeIds => cellTypeIds.map(cellTypeId => `({{${cellTypeId}}})?`).join(" ?") + return `${topHalf} + push: + - match: ${cellTypesToRegex(requiredCellTypeIds)} + captures: +${captures} + - match: $ + pop: true` + } + _getParserInheritanceSet() { + if (!this._cache_parserInheritanceSet) this._cache_parserInheritanceSet = new Set(this.ancestorParserIdsArray) + return this._cache_parserInheritanceSet + } + get ancestorParserIdsArray() { + if (!this._cache_ancestorParserIdsArray) { + this._cache_ancestorParserIdsArray = this._getAncestorsArray().map(def => def.parserIdFromDefinition) + this._cache_ancestorParserIdsArray.reverse() + } + return this._cache_ancestorParserIdsArray + } + get programParserDefinitionCache() { + if (!this._cache_parserDefinitionParsers) this._cache_parserDefinitionParsers = this.isRoot || this.hasParserDefinitions ? this.makeProgramParserDefinitionCache() : this.parent.programParserDefinitionCache + return this._cache_parserDefinitionParsers + } + get hasParserDefinitions() { + return !!this.getChildrenByParser(parserDefinitionParser).length + } + makeProgramParserDefinitionCache() { + const scopedParsers = this.getChildrenByParser(parserDefinitionParser) + const cache = Object.assign({}, this.parent.programParserDefinitionCache) // todo. We don't really need this. we should just lookup the parent if no local hits. + scopedParsers.forEach(parserDefinitionParser => (cache[parserDefinitionParser.parserIdFromDefinition] = parserDefinitionParser)) + return cache + } + get description() { + return this._getFromExtended(ParsersConstants.description) || "" + } + get popularity() { + const val = this._getFromExtended(ParsersConstants.popularity) + return val ? parseFloat(val) : 0 + } + _getExtendedParserId() { + const ancestorIds = this.ancestorParserIdsArray + if (ancestorIds.length > 1) return ancestorIds[ancestorIds.length - 2] + } + _toStumpString() { + const crux = this.cruxIfAny + const cellArray = this.cellParser.getCellArray().filter((item, index) => index) // for now this only works for keyword langs + if (!cellArray.length) + // todo: remove this! just doing it for now until we refactor getCellArray to handle catchAlls better. + return "" + const cells = new Particle(cellArray.map((cell, index) => cell._toStumpInput(crux)).join("\n")) + return `div + label ${crux} +${cells.toString(1)}` + } + toStumpString() { + const particleBreakSymbol = "\n" + return this._getConcreteNonErrorInScopeParticleDefinitions(this._getInScopeParserIds()) + .map(def => def._toStumpString()) + .filter(identity => identity) + .join(particleBreakSymbol) + } + _generateSimulatedLine(seed) { + // todo: generate simulated data from catch all + const crux = this.cruxIfAny + return this.cellParser + .getCellArray() + .map((cell, index) => (!index && crux ? crux : cell.synthesizeCell(seed))) + .join(" ") + } + _shouldSynthesize(def, parserChain) { + if (def._isErrorParser() || def._isAbstract()) return false + if (parserChain.includes(def.id)) return false + const tags = def.get(ParsersConstants.tags) + if (tags && tags.includes(ParsersConstantsMisc.doNotSynthesize)) return false + return true + } + // Get all definitions in this current scope down, even ones that are scoped inside other definitions. + get inScopeAndDescendantDefinitions() { + return this.languageDefinitionProgram._collectAllDefinitions(Object.values(this.programParserDefinitionCache), []) + } + _collectAllDefinitions(defs, collection = []) { + defs.forEach(def => { + collection.push(def) + def._collectAllDefinitions(def.getChildrenByParser(parserDefinitionParser), collection) + }) + return collection + } + get cruxPath() { + const parentPath = this.parent.cruxPath + return (parentPath ? parentPath + " " : "") + this.cruxIfAny + } + get cruxPathAsColumnName() { + return this.cruxPath.replace(/ /g, "_") + } + // Get every definition that extends from this one, even ones that are scoped inside other definitions. + get concreteDescendantDefinitions() { + const { inScopeAndDescendantDefinitions, id } = this + return Object.values(inScopeAndDescendantDefinitions).filter(def => def._doesExtend(id) && !def._isAbstract()) + } + get concreteInScopeDescendantDefinitions() { + // Note: non-recursive. + const defs = this.programParserDefinitionCache + const id = this.id + return Object.values(defs).filter(def => def._doesExtend(id) && !def._isAbstract()) + } + _getConcreteNonErrorInScopeParticleDefinitions(parserIds) { + const defs = [] + parserIds.forEach(parserId => { + const def = this.getParserDefinitionByParserId(parserId) + if (def._isErrorParser()) return + else if (def._isAbstract()) def.concreteInScopeDescendantDefinitions.forEach(def => defs.push(def)) + else defs.push(def) + }) + return defs + } + // todo: refactor + synthesizeParticle(particleCount = 1, indentCount = -1, parsersAlreadySynthesized = [], seed = Date.now()) { + let inScopeParserIds = this._getInScopeParserIds() + const catchAllParserId = this._getFromExtended(ParsersConstants.catchAllParser) + if (catchAllParserId) inScopeParserIds.push(catchAllParserId) + const thisId = this.id + if (!parsersAlreadySynthesized.includes(thisId)) parsersAlreadySynthesized.push(thisId) + const lines = [] + while (particleCount) { + const line = this._generateSimulatedLine(seed) + if (line) lines.push(" ".repeat(indentCount >= 0 ? indentCount : 0) + line) + this._getConcreteNonErrorInScopeParticleDefinitions(inScopeParserIds.filter(parserId => !parsersAlreadySynthesized.includes(parserId))) + .filter(def => this._shouldSynthesize(def, parsersAlreadySynthesized)) + .forEach(def => { + const chain = parsersAlreadySynthesized // .slice(0) + chain.push(def.id) + def.synthesizeParticle(1, indentCount + 1, chain, seed).forEach(line => lines.push(line)) + }) + particleCount-- + } + return lines + } + get cellParser() { + if (!this._cellParser) { + const cellParsingStrategy = this._getFromExtended(ParsersConstants.cellParser) + if (cellParsingStrategy === ParsersCellParser.postfix) this._cellParser = new PostfixCellParser(this) + else if (cellParsingStrategy === ParsersCellParser.omnifix) this._cellParser = new OmnifixCellParser(this) + else this._cellParser = new PrefixCellParser(this) + } + return this._cellParser + } +} +// todo: remove? +class parserDefinitionParser extends AbstractParserDefinitionParser {} +// HandParsersProgram is a constructor that takes a parsers file, and builds a new +// constructor for new language that takes files in that language to execute, compile, etc. +class HandParsersProgram extends AbstractParserDefinitionParser { + createParserCombinator() { + const map = {} + map[ParsersConstants.comment] = Particle + return new Particle.ParserCombinator(UnknownParserParticle, map, [ + { regex: HandParsersProgram.blankLineRegex, parser: Particle }, + { regex: HandParsersProgram.parserFullRegex, parser: parserDefinitionParser }, + { regex: HandParsersProgram.cellTypeFullRegex, parser: cellTypeDefinitionParser } + ]) + } + // rootParser + // Note: this is some so far unavoidable tricky code. We need to eval the transpiled JS, in a NodeJS or browser environment. + _compileAndReturnRootParser() { + if (this._cache_rootParser) return this._cache_rootParser + if (!this.isNodeJs()) { + this._cache_rootParser = Utils.appendCodeAndReturnValueOnWindow(this.toBrowserJavascript(), this.rootParserId).rootParser + return this._cache_rootParser + } + const path = require("path") + const code = this.toNodeJsJavascript(__dirname) + try { + const rootParticle = this._requireInVmNodeJsRootParser(code) + this._cache_rootParser = rootParticle.rootParser + if (!this._cache_rootParser) throw new Error(`Failed to rootParser`) + } catch (err) { + // todo: figure out best error pattern here for debugging + console.log(err) + // console.log(`Error in code: `) + // console.log(new Particle(code).toStringWithLineNumbers()) + } + return this._cache_rootParser + } + get cruxPath() { + return "" + } + trainModel(programs, rootParser = this.compileAndReturnRootParser()) { + const particleDefs = this.validConcreteAndAbstractParserDefinitions + const particleDefCountIncludingRoot = particleDefs.length + 1 + const matrix = Utils.makeMatrix(particleDefCountIncludingRoot, particleDefCountIncludingRoot, 0) + const idToIndex = {} + const indexToId = {} + particleDefs.forEach((def, index) => { + const id = def.id + idToIndex[id] = index + 1 + indexToId[index + 1] = id + }) + programs.forEach(code => { + const exampleProgram = new rootParser(code) + exampleProgram.topDownArray.forEach(particle => { + const particleIndex = idToIndex[particle.definition.id] + const parentParticle = particle.parent + if (!particleIndex) return undefined + if (parentParticle.isRoot()) matrix[0][particleIndex]++ + else { + const parentIndex = idToIndex[parentParticle.definition.id] + if (!parentIndex) return undefined + matrix[parentIndex][particleIndex]++ + } + }) + }) + return { + idToIndex, + indexToId, + matrix + } + } + _mapPredictions(predictionsVector, model) { + const total = Utils.sum(predictionsVector) + const predictions = predictionsVector.slice(1).map((count, index) => { + const id = model.indexToId[index + 1] + return { + id, + def: this.getParserDefinitionByParserId(id), + count, + prob: count / total + } + }) + predictions.sort(Utils.makeSortByFn(prediction => prediction.count)).reverse() + return predictions + } + predictChildren(model, particle) { + return this._mapPredictions(this._predictChildren(model, particle), model) + } + predictParents(model, particle) { + return this._mapPredictions(this._predictParents(model, particle), model) + } + _predictChildren(model, particle) { + return model.matrix[particle.isRoot() ? 0 : model.idToIndex[particle.definition.id]] + } + _predictParents(model, particle) { + if (particle.isRoot()) return [] + const particleIndex = model.idToIndex[particle.definition.id] + return model.matrix.map(row => row[particleIndex]) + } + _setDirName(name) { + this._dirName = name + return this + } + _requireInVmNodeJsRootParser(code) { + const vm = require("vm") + const path = require("path") + // todo: cleanup up + try { + Object.keys(GlobalNamespaceAdditions).forEach(key => { + global[key] = require("./" + GlobalNamespaceAdditions[key]) + }) + global.require = require + global.__dirname = this._dirName + global.module = {} + return vm.runInThisContext(code) + } catch (err) { + // todo: figure out best error pattern here for debugging + console.log(`Error in compiled parsers code for language "${this.parsersName}"`) + // console.log(new Particle(code).toStringWithLineNumbers()) + console.log(err) + throw err + } + } + examplesToTestBlocks(rootParser = this.compileAndReturnRootParser(), expectedErrorMessage = "") { + const testBlocks = {} + this.validConcreteAndAbstractParserDefinitions.forEach(def => + def.examples.forEach(example => { + const id = def.id + example.content + testBlocks[id] = equal => { + const exampleProgram = new rootParser(example.childrenToString()) + const errors = exampleProgram.getAllErrors(example._getLineNumber() + 1) + equal(errors.join("\n"), expectedErrorMessage, `Expected no errors in ${id}`) + } + }) + ) + return testBlocks + } + toReadMe() { + const languageName = this.extensionName + const rootParticleDef = this.rootParserDefinition + const cellTypes = this.cellTypeDefinitions + const parserLineage = this.parserLineage + const exampleParticle = rootParticleDef.examples[0] + return `title ${languageName} Readme + +paragraph ${rootParticleDef.description} + +subtitle Quick Example + +code +${exampleParticle ? exampleParticle.childrenToString(1) : ""} + +subtitle Quick facts about ${languageName} + +list + - ${languageName} has ${parserLineage.topDownArray.length} particle types. + - ${languageName} has ${Object.keys(cellTypes).length} cell types + - The source code for ${languageName} is ${this.topDownArray.length} lines long. + +subtitle Installing + +code + npm install . + +subtitle Testing + +code + node test.js + +subtitle Parsers + +code +${parserLineage.toString(1)} + +subtitle Cell Types + +code +${new Particle(Object.keys(cellTypes).join("\n")).toString(1)} + +subtitle Road Map + +paragraph Here are the "todos" present in the source code for ${languageName}: + +list +${this.topDownArray + .filter(particle => particle.getWord(0) === "todo") + .map(particle => ` - ${particle.getLine()}`) + .join("\n")} + +paragraph This readme was auto-generated using the + link https://github.com/breck7/scrollsdk ScrollSDK.` + } + toBundle() { + const files = {} + const rootParticleDef = this.rootParserDefinition + const languageName = this.extensionName + const example = rootParticleDef.examples[0] + const sampleCode = example ? example.childrenToString() : "" + files[ParsersBundleFiles.package] = JSON.stringify( + { + name: languageName, + private: true, + dependencies: { + scrollsdk: Particle.getVersion() + } + }, + null, + 2 + ) + files[ParsersBundleFiles.readme] = this.toReadMe() + const testCode = `const program = new ${languageName}(sampleCode) +const errors = program.getAllErrors() +console.log("Sample program compiled with " + errors.length + " errors.") +if (errors.length) + console.log(errors.map(error => error.message))` + const nodePath = `${languageName}.node.js` + files[nodePath] = this.toNodeJsJavascript() + files[ParsersBundleFiles.indexJs] = `module.exports = require("./${nodePath}")` + const browserPath = `${languageName}.browser.js` + files[browserPath] = this.toBrowserJavascript() + files[ParsersBundleFiles.indexHtml] = ` + + + +` + const samplePath = "sample." + this.extensionName + files[samplePath] = sampleCode.toString() + files[ParsersBundleFiles.testJs] = `const ${languageName} = require("./index.js") +/*keep-line*/ const sampleCode = require("fs").readFileSync("${samplePath}", "utf8") +${testCode}` + return files + } + get targetExtension() { + return this.rootParserDefinition.get(ParsersConstants.compilesTo) + } + get cellTypeDefinitions() { + if (this._cache_cellTypes) return this._cache_cellTypes + const types = {} + // todo: add built in word types? + this.getChildrenByParser(cellTypeDefinitionParser).forEach(type => (types[type.cellTypeId] = type)) + this._cache_cellTypes = types + return types + } + getCellTypeDefinitionById(cellTypeId) { + // todo: return unknownCellTypeDefinition? or is that handled somewhere else? + return this.cellTypeDefinitions[cellTypeId] + } + get parserLineage() { + const newParticle = new Particle() + Object.values(this.validConcreteAndAbstractParserDefinitions).forEach(particle => newParticle.touchParticle(particle.ancestorParserIdsArray.join(" "))) + return newParticle + } + get languageDefinitionProgram() { + return this + } + get validConcreteAndAbstractParserDefinitions() { + return this.getChildrenByParser(parserDefinitionParser).filter(particle => particle._hasValidParserId()) + } + get lastRootParserDefinitionParticle() { + return this.findLast(def => def instanceof AbstractParserDefinitionParser && def.has(ParsersConstants.root) && def._hasValidParserId()) + } + _initRootParserDefinitionParticle() { + if (this._cache_rootParserParticle) return + if (!this._cache_rootParserParticle) this._cache_rootParserParticle = this.lastRootParserDefinitionParticle + // By default, have a very permissive basic root particle. + // todo: whats the best design pattern to use for this sort of thing? + if (!this._cache_rootParserParticle) { + this._cache_rootParserParticle = this.concat(`${ParsersConstants.DefaultRootParser} + ${ParsersConstants.root} + ${ParsersConstants.catchAllParser} ${ParsersConstants.BlobParser}`)[0] + this._addDefaultCatchAllBlobParser() + } + } + get rootParserDefinition() { + this._initRootParserDefinitionParticle() + return this._cache_rootParserParticle + } + _addDefaultCatchAllBlobParser() { + if (this._addedCatchAll) return + this._addedCatchAll = true + delete this._cache_parserDefinitionParsers + this.concat(`${ParsersConstants.BlobParser} + ${ParsersConstants.baseParser} ${ParsersConstants.blobParser}`) + } + get extensionName() { + return this.parsersName + } + get id() { + return this.rootParserId + } + get rootParserId() { + return this.rootParserDefinition.parserIdFromDefinition + } + get parsersName() { + return this.rootParserId.replace(HandParsersProgram.parserSuffixRegex, "") + } + _getMyInScopeParserIds() { + return super._getMyInScopeParserIds(this.rootParserDefinition) + } + _getInScopeParserIds() { + const parsersParticle = this.rootParserDefinition.getParticle(ParsersConstants.inScope) + return parsersParticle ? parsersParticle.getWordsFrom(1) : [] + } + makeProgramParserDefinitionCache() { + const cache = {} + this.getChildrenByParser(parserDefinitionParser).forEach(parserDefinitionParser => (cache[parserDefinitionParser.parserIdFromDefinition] = parserDefinitionParser)) + return cache + } + compileAndReturnRootParser() { + if (!this._cached_rootParser) { + const rootDef = this.rootParserDefinition + this._cached_rootParser = rootDef.languageDefinitionProgram._compileAndReturnRootParser() + } + return this._cached_rootParser + } + get fileExtensions() { + return this.rootParserDefinition.get(ParsersConstants.extensions) ? this.rootParserDefinition.get(ParsersConstants.extensions).split(" ").join(",") : this.extensionName + } + toNodeJsJavascript(scrollsdkProductsPath = "scrollsdk/products") { + return this._rootParticleDefToJavascriptClass(scrollsdkProductsPath, true).trim() + } + toBrowserJavascript() { + return this._rootParticleDefToJavascriptClass("", false).trim() + } + _rootParticleDefToJavascriptClass(scrollsdkProductsPath, forNodeJs = true) { + const defs = this.validConcreteAndAbstractParserDefinitions + // todo: throw if there is no root particle defined + const parserClasses = defs.map(def => def.asJavascriptClass).join("\n\n") + const rootDef = this.rootParserDefinition + const rootNodeJsHeader = forNodeJs && rootDef._getConcatBlockStringFromExtended(ParsersConstants._rootNodeJsHeader) + const rootName = rootDef.generatedClassName + if (!rootName) throw new Error(`Root Particle Type Has No Name`) + let exportScript = "" + if (forNodeJs) + exportScript = `module.exports = ${rootName}; +${rootName}` + else exportScript = `window.${rootName} = ${rootName}` + let nodeJsImports = `` + if (forNodeJs) { + const path = require("path") + nodeJsImports = Object.keys(GlobalNamespaceAdditions) + .map(key => { + const thePath = scrollsdkProductsPath + "/" + GlobalNamespaceAdditions[key] + return `const { ${key} } = require("${thePath.replace(/\\/g, "\\\\")}")` // escape windows backslashes + }) + .join("\n") + } + // todo: we can expose the previous "constants" export, if needed, via the parsers, which we preserve. + return `{ +${nodeJsImports} +${rootNodeJsHeader ? rootNodeJsHeader : ""} +${parserClasses} + +${exportScript} +} +` + } + toSublimeSyntaxFile() { + const cellTypeDefs = this.cellTypeDefinitions + const variables = Object.keys(cellTypeDefs) + .map(name => ` ${name}: '${cellTypeDefs[name].regexString}'`) + .join("\n") + const defs = this.validConcreteAndAbstractParserDefinitions.filter(kw => !kw._isAbstract()) + const parserContexts = defs.map(def => def._toSublimeMatchBlock()).join("\n\n") + const includes = defs.map(parserDef => ` - include: '${parserDef.parserIdFromDefinition}'`).join("\n") + return `%YAML 1.2 +--- +name: ${this.extensionName} +file_extensions: [${this.fileExtensions}] +scope: source.${this.extensionName} + +variables: +${variables} + +contexts: + main: +${includes} + +${parserContexts}` + } +} +HandParsersProgram.makeParserId = str => Utils._replaceNonAlphaNumericCharactersWithCharCodes(str).replace(HandParsersProgram.parserSuffixRegex, "") + ParsersConstants.parserSuffix +HandParsersProgram.makeCellTypeId = str => Utils._replaceNonAlphaNumericCharactersWithCharCodes(str).replace(HandParsersProgram.cellTypeSuffixRegex, "") + ParsersConstants.cellTypeSuffix +HandParsersProgram.parserSuffixRegex = new RegExp(ParsersConstants.parserSuffix + "$") +HandParsersProgram.parserFullRegex = new RegExp("^[a-zA-Z0-9_]+" + ParsersConstants.parserSuffix + "$") +HandParsersProgram.blankLineRegex = new RegExp("^$") +HandParsersProgram.cellTypeSuffixRegex = new RegExp(ParsersConstants.cellTypeSuffix + "$") +HandParsersProgram.cellTypeFullRegex = new RegExp("^[a-zA-Z0-9_]+" + ParsersConstants.cellTypeSuffix + "$") +HandParsersProgram._languages = {} +HandParsersProgram._parsers = {} +const PreludeKinds = {} +PreludeKinds[PreludeCellTypeIds.anyCell] = ParsersAnyCell +PreludeKinds[PreludeCellTypeIds.keywordCell] = ParsersKeywordCell +PreludeKinds[PreludeCellTypeIds.floatCell] = ParsersFloatCell +PreludeKinds[PreludeCellTypeIds.numberCell] = ParsersFloatCell +PreludeKinds[PreludeCellTypeIds.bitCell] = ParsersBitCell +PreludeKinds[PreludeCellTypeIds.boolCell] = ParsersBoolCell +PreludeKinds[PreludeCellTypeIds.intCell] = ParsersIntCell +class UnknownParsersProgram extends Particle { + _inferRootParticleForAPrefixLanguage(parsersName) { + parsersName = HandParsersProgram.makeParserId(parsersName) + const rootParticle = new Particle(`${parsersName} + ${ParsersConstants.root}`) + // note: right now we assume 1 global cellTypeMap and parserMap per parsers. But we may have scopes in the future? + const rootParticleNames = this.getFirstWords() + .filter(identity => identity) + .map(word => HandParsersProgram.makeParserId(word)) + rootParticle + .particleAt(0) + .touchParticle(ParsersConstants.inScope) + .setWordsFrom(1, Array.from(new Set(rootParticleNames))) + return rootParticle + } + _renameIntegerKeywords(clone) { + // todo: why are we doing this? + for (let particle of clone.getTopDownArrayIterator()) { + const firstWordIsAnInteger = !!particle.firstWord.match(/^\d+$/) + const parentFirstWord = particle.parent.firstWord + if (firstWordIsAnInteger && parentFirstWord) particle.setFirstWord(HandParsersProgram.makeParserId(parentFirstWord + UnknownParsersProgram._childSuffix)) + } + } + _getKeywordMaps(clone) { + const keywordsToChildKeywords = {} + const keywordsToParticleInstances = {} + for (let particle of clone.getTopDownArrayIterator()) { + const firstWord = particle.firstWord + if (!keywordsToChildKeywords[firstWord]) keywordsToChildKeywords[firstWord] = {} + if (!keywordsToParticleInstances[firstWord]) keywordsToParticleInstances[firstWord] = [] + keywordsToParticleInstances[firstWord].push(particle) + particle.forEach(child => (keywordsToChildKeywords[firstWord][child.firstWord] = true)) + } + return { keywordsToChildKeywords: keywordsToChildKeywords, keywordsToParticleInstances: keywordsToParticleInstances } + } + _inferParserDef(firstWord, globalCellTypeMap, childFirstWords, instances) { + const edgeSymbol = this.edgeSymbol + const parserId = HandParsersProgram.makeParserId(firstWord) + const particleDefParticle = new Particle(parserId).particleAt(0) + const childParserIds = childFirstWords.map(word => HandParsersProgram.makeParserId(word)) + if (childParserIds.length) particleDefParticle.touchParticle(ParsersConstants.inScope).setWordsFrom(1, childParserIds) + const cellsForAllInstances = instances + .map(line => line.content) + .filter(identity => identity) + .map(line => line.split(edgeSymbol)) + const instanceCellCounts = new Set(cellsForAllInstances.map(cells => cells.length)) + const maxCellsOnLine = Math.max(...Array.from(instanceCellCounts)) + const minCellsOnLine = Math.min(...Array.from(instanceCellCounts)) + let catchAllCellType + let cellTypeIds = [] + for (let cellIndex = 0; cellIndex < maxCellsOnLine; cellIndex++) { + const cellType = this._getBestCellType( + firstWord, + instances.length, + maxCellsOnLine, + cellsForAllInstances.map(cells => cells[cellIndex]) + ) + if (!globalCellTypeMap.has(cellType.cellTypeId)) globalCellTypeMap.set(cellType.cellTypeId, cellType.cellTypeDefinition) + cellTypeIds.push(cellType.cellTypeId) + } + if (maxCellsOnLine > minCellsOnLine) { + //columns = columns.slice(0, min) + catchAllCellType = cellTypeIds.pop() + while (cellTypeIds[cellTypeIds.length - 1] === catchAllCellType) { + cellTypeIds.pop() + } + } + const needsCruxProperty = !firstWord.endsWith(UnknownParsersProgram._childSuffix + ParsersConstants.parserSuffix) // todo: cleanup + if (needsCruxProperty) particleDefParticle.set(ParsersConstants.crux, firstWord) + if (catchAllCellType) particleDefParticle.set(ParsersConstants.catchAllCellType, catchAllCellType) + const cellLine = cellTypeIds.slice() + cellLine.unshift(PreludeCellTypeIds.keywordCell) + if (cellLine.length > 0) particleDefParticle.set(ParsersConstants.cells, cellLine.join(edgeSymbol)) + //if (!catchAllCellType && cellTypeIds.length === 1) particleDefParticle.set(ParsersConstants.cells, cellTypeIds[0]) + // Todo: add conditional frequencies + return particleDefParticle.parent.toString() + } + // inferParsersFileForAnSSVLanguage(parsersName: string): string { + // parsersName = HandParsersProgram.makeParserId(parsersName) + // const rootParticle = new Particle(`${parsersName} + // ${ParsersConstants.root}`) + // // note: right now we assume 1 global cellTypeMap and parserMap per parsers. But we may have scopes in the future? + // const rootParticleNames = this.getFirstWords().map(word => HandParsersProgram.makeParserId(word)) + // rootParticle + // .particleAt(0) + // .touchParticle(ParsersConstants.inScope) + // .setWordsFrom(1, Array.from(new Set(rootParticleNames))) + // return rootParticle + // } + inferParsersFileForAKeywordLanguage(parsersName) { + const clone = this.clone() + this._renameIntegerKeywords(clone) + const { keywordsToChildKeywords, keywordsToParticleInstances } = this._getKeywordMaps(clone) + const globalCellTypeMap = new Map() + globalCellTypeMap.set(PreludeCellTypeIds.keywordCell, undefined) + const parserDefs = Object.keys(keywordsToChildKeywords) + .filter(identity => identity) + .map(firstWord => this._inferParserDef(firstWord, globalCellTypeMap, Object.keys(keywordsToChildKeywords[firstWord]), keywordsToParticleInstances[firstWord])) + const cellTypeDefs = [] + globalCellTypeMap.forEach((def, id) => cellTypeDefs.push(def ? def : id)) + const particleBreakSymbol = this.particleBreakSymbol + return this._formatCode([this._inferRootParticleForAPrefixLanguage(parsersName).toString(), cellTypeDefs.join(particleBreakSymbol), parserDefs.join(particleBreakSymbol)].filter(identity => identity).join("\n")) + } + _formatCode(code) { + // todo: make this run in browser too + if (!this.isNodeJs()) return code + const parsersProgram = new HandParsersProgram(Particle.fromDisk(__dirname + "/../langs/parsers/parsers.parsers")) + const rootParser = parsersProgram.compileAndReturnRootParser() + const program = new rootParser(code) + return program.format().toString() + } + _getBestCellType(firstWord, instanceCount, maxCellsOnLine, allValues) { + const asSet = new Set(allValues) + const edgeSymbol = this.edgeSymbol + const values = Array.from(asSet).filter(identity => identity) + const every = fn => { + for (let index = 0; index < values.length; index++) { + if (!fn(values[index])) return false + } + return true + } + if (every(str => str === "0" || str === "1")) return { cellTypeId: PreludeCellTypeIds.bitCell } + if ( + every(str => { + const num = parseInt(str) + if (isNaN(num)) return false + return num.toString() === str + }) + ) { + return { cellTypeId: PreludeCellTypeIds.intCell } + } + if (every(str => str.match(/^-?\d*.?\d+$/))) return { cellTypeId: PreludeCellTypeIds.floatCell } + const bools = new Set(["1", "0", "true", "false", "t", "f", "yes", "no"]) + if (every(str => bools.has(str.toLowerCase()))) return { cellTypeId: PreludeCellTypeIds.boolCell } + // todo: cleanup + const enumLimit = 30 + if (instanceCount > 1 && maxCellsOnLine === 1 && allValues.length > asSet.size && asSet.size < enumLimit) + return { + cellTypeId: HandParsersProgram.makeCellTypeId(firstWord), + cellTypeDefinition: `${HandParsersProgram.makeCellTypeId(firstWord)} + enum ${values.join(edgeSymbol)}` + } + return { cellTypeId: PreludeCellTypeIds.anyCell } + } +} +UnknownParsersProgram._childSuffix = "Child" +window.ParsersConstants = ParsersConstants +window.PreludeCellTypeIds = PreludeCellTypeIds +window.HandParsersProgram = HandParsersProgram +window.ParserBackedParticle = ParserBackedParticle +window.UnknownParserError = UnknownParserError +window.UnknownParsersProgram = UnknownParsersProgram + + +"use strict" +// Adapted from https://github.com/NeekSandhu/codemirror-textmate/blob/master/src/tmToCm.ts +var CmToken +;(function (CmToken) { + CmToken["Atom"] = "atom" + CmToken["Attribute"] = "attribute" + CmToken["Bracket"] = "bracket" + CmToken["Builtin"] = "builtin" + CmToken["Comment"] = "comment" + CmToken["Def"] = "def" + CmToken["Error"] = "error" + CmToken["Header"] = "header" + CmToken["HR"] = "hr" + CmToken["Keyword"] = "keyword" + CmToken["Link"] = "link" + CmToken["Meta"] = "meta" + CmToken["Number"] = "number" + CmToken["Operator"] = "operator" + CmToken["Property"] = "property" + CmToken["Qualifier"] = "qualifier" + CmToken["Quote"] = "quote" + CmToken["String"] = "string" + CmToken["String2"] = "string-2" + CmToken["Tag"] = "tag" + CmToken["Type"] = "type" + CmToken["Variable"] = "variable" + CmToken["Variable2"] = "variable-2" + CmToken["Variable3"] = "variable-3" +})(CmToken || (CmToken = {})) +const tmToCm = { + comment: { + $: CmToken.Comment + }, + constant: { + // TODO: Revision + $: CmToken.Def, + character: { + escape: { + $: CmToken.String2 + } + }, + language: { + $: CmToken.Atom + }, + numeric: { + $: CmToken.Number + }, + other: { + email: { + link: { + $: CmToken.Link + } + }, + symbol: { + // TODO: Revision + $: CmToken.Def + } + } + }, + entity: { + name: { + class: { + $: CmToken.Def + }, + function: { + $: CmToken.Def + }, + tag: { + $: CmToken.Tag + }, + type: { + $: CmToken.Type, + class: { + $: CmToken.Variable + } + } + }, + other: { + "attribute-name": { + $: CmToken.Attribute + }, + "inherited-class": { + // TODO: Revision + $: CmToken.Def + } + }, + support: { + function: { + // TODO: Revision + $: CmToken.Def + } + } + }, + invalid: { + $: CmToken.Error, + illegal: { $: CmToken.Error }, + deprecated: { + $: CmToken.Error + } + }, + keyword: { + $: CmToken.Keyword, + operator: { + $: CmToken.Operator + }, + other: { + "special-method": CmToken.Def + } + }, + punctuation: { + $: CmToken.Operator, + definition: { + comment: { + $: CmToken.Comment + }, + tag: { + $: CmToken.Bracket + } + // 'template-expression': { + // $: CodeMirrorToken.Operator, + // }, + } + // terminator: { + // $: CodeMirrorToken.Operator, + // }, + }, + storage: { + $: CmToken.Keyword + }, + string: { + $: CmToken.String, + regexp: { + $: CmToken.String2 + } + }, + support: { + class: { + $: CmToken.Def + }, + constant: { + $: CmToken.Variable2 + }, + function: { + $: CmToken.Def + }, + type: { + $: CmToken.Type + }, + variable: { + $: CmToken.Variable2, + property: { + $: CmToken.Property + } + } + }, + variable: { + $: CmToken.Def, + language: { + // TODO: Revision + $: CmToken.Variable3 + }, + other: { + object: { + $: CmToken.Variable, + property: { + $: CmToken.Property + } + }, + property: { + $: CmToken.Property + } + }, + parameter: { + $: CmToken.Def + } + } +} +const textMateScopeToCodeMirrorStyle = (scopeSegments, style = tmToCm) => { + const matchingBranch = style[scopeSegments.shift()] + return matchingBranch ? textMateScopeToCodeMirrorStyle(scopeSegments, matchingBranch) || matchingBranch.$ || null : null +} +class ParsersCodeMirrorMode { + constructor(name, getRootParserFn, getProgramCodeFn, codeMirrorLib = undefined) { + this._name = name + this._getRootParserFn = getRootParserFn + this._getProgramCodeFn = getProgramCodeFn || (instance => (instance ? instance.getValue() : this._originalValue)) + this._codeMirrorLib = codeMirrorLib + } + _getParsedProgram() { + const source = this._getProgramCodeFn(this._cmInstance) || "" + if (!this._cachedProgram || this._cachedSource !== source) { + this._cachedSource = source + this._cachedProgram = new (this._getRootParserFn())(source) + } + return this._cachedProgram + } + _getExcludedIntelliSenseTriggerKeys() { + return { + 8: "backspace", + 9: "tab", + 13: "enter", + 16: "shift", + 17: "ctrl", + 18: "alt", + 19: "pause", + 20: "capslock", + 27: "escape", + 33: "pageup", + 34: "pagedown", + 35: "end", + 36: "home", + 37: "left", + 38: "up", + 39: "right", + 40: "down", + 45: "insert", + 46: "delete", + 91: "left window key", + 92: "right window key", + 93: "select", + 112: "f1", + 113: "f2", + 114: "f3", + 115: "f4", + 116: "f5", + 117: "f6", + 118: "f7", + 119: "f8", + 120: "f9", + 121: "f10", + 122: "f11", + 123: "f12", + 144: "numlock", + 145: "scrolllock" + } + } + token(stream, state) { + return this._advanceStreamAndReturnTokenType(stream, state) + } + fromTextAreaWithAutocomplete(area, options) { + this._originalValue = area.value + const defaultOptions = { + lineNumbers: true, + mode: this._name, + tabSize: 1, + indentUnit: 1, + hintOptions: { + hint: (cmInstance, options) => this.codeMirrorAutocomplete(cmInstance, options) + } + } + Object.assign(defaultOptions, options) + this._cmInstance = this._getCodeMirrorLib().fromTextArea(area, defaultOptions) + this._enableAutoComplete(this._cmInstance) + return this._cmInstance + } + _enableAutoComplete(cmInstance) { + const excludedKeys = this._getExcludedIntelliSenseTriggerKeys() + const codeMirrorLib = this._getCodeMirrorLib() + cmInstance.on("keyup", (cm, event) => { + // https://stackoverflow.com/questions/13744176/codemirror-autocomplete-after-any-keyup + if (!cm.state.completionActive && !excludedKeys[event.keyCode.toString()]) + // Todo: get typings for CM autocomplete + codeMirrorLib.commands.autocomplete(cm, null, { completeSingle: false }) + }) + } + _getCodeMirrorLib() { + return this._codeMirrorLib + } + async codeMirrorAutocomplete(cmInstance, options) { + const cursor = cmInstance.getDoc().getCursor() + const codeMirrorLib = this._getCodeMirrorLib() + const result = await this._getParsedProgram().getAutocompleteResultsAt(cursor.line, cursor.ch) + // It seems to be better UX if there's only 1 result, and its the word the user entered, to close autocomplete + if (result.matches.length === 1 && result.matches[0].text === result.word) return null + return result.matches.length + ? { + list: result.matches, + from: codeMirrorLib.Pos(cursor.line, result.startCharIndex), + to: codeMirrorLib.Pos(cursor.line, result.endCharIndex) + } + : null + } + register() { + const codeMirrorLib = this._getCodeMirrorLib() + codeMirrorLib.defineMode(this._name, () => this) + codeMirrorLib.defineMIME("text/" + this._name, this._name) + return this + } + _advanceStreamAndReturnTokenType(stream, state) { + let nextCharacter = stream.next() + const lineNumber = stream.lineOracle.line + 1 // state.lineIndex + const WordBreakSymbol = " " + const ParticleBreakSymbol = "\n" + while (typeof nextCharacter === "string") { + const peek = stream.peek() + if (nextCharacter === WordBreakSymbol) { + if (peek === undefined || peek === ParticleBreakSymbol) { + stream.skipToEnd() // advance string to end + this._incrementLine(state) + } + if (peek === WordBreakSymbol && state.cellIndex) { + // If we are missing a cell. + // TODO: this is broken for a blank 1st cell. We need to track WordBreakSymbol level. + state.cellIndex++ + } + return "bracket" + } + if (peek === WordBreakSymbol) { + state.cellIndex++ + return this._getCellStyle(lineNumber, state.cellIndex) + } + nextCharacter = stream.next() + } + state.cellIndex++ + const style = this._getCellStyle(lineNumber, state.cellIndex) + this._incrementLine(state) + return style + } + _getCellStyle(lineIndex, cellIndex) { + const program = this._getParsedProgram() + // todo: if the current word is an error, don't show red? + if (!program.getCellPaintAtPosition) console.log(program) + const paint = program.getCellPaintAtPosition(lineIndex, cellIndex) + const style = paint ? textMateScopeToCodeMirrorStyle(paint.split(".")) : undefined + return style || "noPaintDefinedInParsers" + } + // todo: remove. + startState() { + return { + cellIndex: 0 + } + } + _incrementLine(state) { + state.cellIndex = 0 + } +} +window.ParsersCodeMirrorMode = ParsersCodeMirrorMode + + +{ + class stumpParser extends ParserBackedParticle { + createParserCombinator() { + return new Particle.ParserCombinator( + errorParser, + Object.assign(Object.assign({}, super.createParserCombinator()._getFirstWordMapAsObject()), { + blockquote: htmlTagParser, + colgroup: htmlTagParser, + datalist: htmlTagParser, + fieldset: htmlTagParser, + menuitem: htmlTagParser, + noscript: htmlTagParser, + optgroup: htmlTagParser, + progress: htmlTagParser, + styleTag: htmlTagParser, + template: htmlTagParser, + textarea: htmlTagParser, + titleTag: htmlTagParser, + address: htmlTagParser, + article: htmlTagParser, + caption: htmlTagParser, + details: htmlTagParser, + section: htmlTagParser, + summary: htmlTagParser, + button: htmlTagParser, + canvas: htmlTagParser, + dialog: htmlTagParser, + figure: htmlTagParser, + footer: htmlTagParser, + header: htmlTagParser, + hgroup: htmlTagParser, + iframe: htmlTagParser, + keygen: htmlTagParser, + legend: htmlTagParser, + object: htmlTagParser, + option: htmlTagParser, + output: htmlTagParser, + script: htmlTagParser, + select: htmlTagParser, + source: htmlTagParser, + strong: htmlTagParser, + aside: htmlTagParser, + embed: htmlTagParser, + input: htmlTagParser, + label: htmlTagParser, + meter: htmlTagParser, + param: htmlTagParser, + small: htmlTagParser, + table: htmlTagParser, + tbody: htmlTagParser, + tfoot: htmlTagParser, + thead: htmlTagParser, + track: htmlTagParser, + video: htmlTagParser, + abbr: htmlTagParser, + area: htmlTagParser, + base: htmlTagParser, + body: htmlTagParser, + code: htmlTagParser, + form: htmlTagParser, + head: htmlTagParser, + html: htmlTagParser, + link: htmlTagParser, + main: htmlTagParser, + mark: htmlTagParser, + menu: htmlTagParser, + meta: htmlTagParser, + ruby: htmlTagParser, + samp: htmlTagParser, + span: htmlTagParser, + time: htmlTagParser, + bdi: htmlTagParser, + bdo: htmlTagParser, + col: htmlTagParser, + del: htmlTagParser, + dfn: htmlTagParser, + div: htmlTagParser, + img: htmlTagParser, + ins: htmlTagParser, + kbd: htmlTagParser, + map: htmlTagParser, + nav: htmlTagParser, + pre: htmlTagParser, + rtc: htmlTagParser, + sub: htmlTagParser, + sup: htmlTagParser, + var: htmlTagParser, + wbr: htmlTagParser, + br: htmlTagParser, + dd: htmlTagParser, + dl: htmlTagParser, + dt: htmlTagParser, + em: htmlTagParser, + h1: htmlTagParser, + h2: htmlTagParser, + h3: htmlTagParser, + h4: htmlTagParser, + h5: htmlTagParser, + h6: htmlTagParser, + hr: htmlTagParser, + li: htmlTagParser, + ol: htmlTagParser, + rb: htmlTagParser, + rp: htmlTagParser, + rt: htmlTagParser, + td: htmlTagParser, + th: htmlTagParser, + tr: htmlTagParser, + ul: htmlTagParser, + a: htmlTagParser, + b: htmlTagParser, + i: htmlTagParser, + p: htmlTagParser, + q: htmlTagParser, + s: htmlTagParser, + u: htmlTagParser + }), + [ + { regex: /^$/, parser: blankLineParser }, + { regex: /^[a-zA-Z0-9_]+Component/, parser: componentDefinitionParser } + ] + ) + } + compile() { + return this.asHtml + } + _getHtmlJoinByCharacter() { + return "" + } + static cachedHandParsersProgramRoot = new HandParsersProgram(`// Cell parsers +anyCell +keywordCell +emptyCell +extraCell + paint invalid +anyHtmlContentCell + paint string +attributeValueCell + paint constant.language +componentTagNameCell + paint variable.function + extends keywordCell +htmlTagNameCell + paint variable.function + extends keywordCell + enum a abbr address area article aside b base bdi bdo blockquote body br button canvas caption code col colgroup datalist dd del details dfn dialog div dl dt em embed fieldset figure footer form h1 h2 h3 h4 h5 h6 head header hgroup hr html i iframe img input ins kbd keygen label legend li link main map mark menu menuitem meta meter nav noscript object ol optgroup option output p param pre progress q rb rp rt rtc ruby s samp script section select small source span strong styleTag sub summary sup table tbody td template textarea tfoot th thead time titleTag tr track u ul var video wbr +htmlAttributeNameCell + paint entity.name.type + extends keywordCell + enum accept accept-charset accesskey action align alt async autocomplete autofocus autoplay bgcolor border charset checked class color cols colspan content contenteditable controls coords datetime default defer dir dirname disabled download draggable dropzone enctype for formaction headers height hidden high href hreflang http-equiv id ismap kind lang list loop low max maxlength media method min multiple muted name novalidate onabort onafterprint onbeforeprint onbeforeunload onblur oncanplay oncanplaythrough onchange onclick oncontextmenu oncopy oncuechange oncut ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended onerror onfocus onhashchange oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart onmousedown onmousemove onmouseout onmouseover onmouseup onmousewheel onoffline ononline onpagehide onpageshow onpaste onpause onplay onplaying onpopstate onprogress onratechange onreset onresize onscroll onsearch onseeked onseeking onselect onstalled onstorage onsubmit onsuspend ontimeupdate ontoggle onunload onvolumechange onwaiting onwheel open optimum pattern placeholder poster preload property readonly rel required reversed rows rowspan sandbox scope selected shape size sizes spellcheck src srcdoc srclang srcset start step style tabindex target title translate type usemap value width wrap +bernKeywordCell + enum bern + extends keywordCell + +// Line parsers +stumpParser + root + description A prefix Language that compiles to HTML. + catchAllParser errorParser + inScope htmlTagParser blankLineParser + example + div + h1 hello world + compilesTo html + javascript + compile() { + return this.asHtml + } + _getHtmlJoinByCharacter() { + return "" + } +blankLineParser + pattern ^$ + tags doNotSynthesize + cells emptyCell + javascript + _toHtml() { + return "" + } + getTextContent() {return ""} +htmlTagParser + inScope bernParser htmlTagParser htmlAttributeParser blankLineParser + catchAllCellType anyHtmlContentCell + cells htmlTagNameCell + javascript + isHtmlTagParser = true + getTag() { + // we need to remove the "Tag" bit to handle the style and title attribute/tag conflict. + const firstWord = this.firstWord + const map = { + titleTag: "title", + styleTag: "style" + } + return map[firstWord] || firstWord + } + _getHtmlJoinByCharacter() { + return "" + } + asHtmlWithSuids() { + return this._toHtml(undefined, true) + } + _getOneLiner() { + const oneLinerWords = this.getWordsFrom(1) + return oneLinerWords.length ? oneLinerWords.join(" ") : "" + } + getTextContent() { + return this._getOneLiner() + } + shouldCollapse() { + return this.has("collapse") + } + get domElement() { + var elem = document.createElement(this.getTag()) + elem.setAttribute("stumpUid", this._getUid()) + this.filter(particle => particle.isAttributeParser) + .forEach(child => elem.setAttribute(child.firstWord, child.content)) + elem.innerHTML = this.has("bern") ? this.getParticle("bern").childrenToString() : this._getOneLiner() + this.filter(particle => particle.isHtmlTagParser) + .forEach(child => elem.appendChild(child.domElement)) + return elem + } + _toHtml(indentCount, withSuid) { + const tag = this.getTag() + const children = this.map(child => child._toHtml(indentCount + 1, withSuid)).join("") + const attributesStr = this.filter(particle => particle.isAttributeParser) + .map(child => child.getAttribute()) + .join("") + const indent = " ".repeat(indentCount) + const collapse = this.shouldCollapse() + const indentForChildParsers = !collapse && this.getChildInstancesOfParserId("htmlTagParser").length > 0 + const suid = withSuid ? \` stumpUid="\${this._getUid()}"\` : "" + const oneLiner = this._getOneLiner() + return \`\${!collapse ? indent : ""}<\${tag}\${attributesStr}\${suid}>\${oneLiner}\${indentForChildParsers ? "\\n" : ""}\${children}\${collapse ? "" : "\\n"}\` + } + removeCssStumpParticle() { + return this.removeStumpParticle() + } + removeStumpParticle() { + this.getShadow().removeShadow() + return this.destroy() + } + getParticleByGuid(guid) { + return this.topDownArray.find(particle => particle._getUid() === guid) + } + addClassToStumpParticle(className) { + const classParser = this.touchParticle("class") + const words = classParser.getWordsFrom(1) + // note: we call add on shadow regardless, because at the moment stump may have gotten out of + // sync with shadow, if things modified the dom. todo: cleanup. + this.getShadow().addClassToShadow(className) + if (words.includes(className)) return this + words.push(className) + classParser.setContent(words.join(this.wordBreakSymbol)) + return this + } + removeClassFromStumpParticle(className) { + const classParser = this.getParticle("class") + if (!classParser) return this + const newClasses = classParser.words.filter(word => word !== className) + if (!newClasses.length) classParser.destroy() + else classParser.setContent(newClasses.join(" ")) + this.getShadow().removeClassFromShadow(className) + return this + } + stumpParticleHasClass(className) { + const classParser = this.getParticle("class") + return classParser && classParser.words.includes(className) ? true : false + } + isStumpParticleCheckbox() { + return this.get("type") === "checkbox" + } + getShadow() { + if (!this._shadow) { + const shadowClass = this.getShadowClass() + this._shadow = new shadowClass(this) + } + return this._shadow + } + insertCssChildParticle(text, index) { + return this.insertChildParticle(text, index) + } + insertChildParticle(text, index) { + const singleParticle = new Particle(text).getChildren()[0] + const newParticle = this.insertLineAndChildren(singleParticle.getLine(), singleParticle.childrenToString(), index) + const stumpParserIndex = this.filter(particle => particle.isHtmlTagParser).indexOf(newParticle) + this.getShadow().insertHtmlParticle(newParticle, stumpParserIndex) + return newParticle + } + isInputType() { + return ["input", "textarea"].includes(this.getTag()) || this.get("contenteditable") === "true" + } + findStumpParticleByChild(line) { + return this.findStumpParticlesByChild(line)[0] + } + findStumpParticleByChildString(line) { + return this.topDownArray.find(particle => + particle + .map(child => child.getLine()) + .join("\\n") + .includes(line) + ) + } + findStumpParticleByFirstWord(firstWord) { + return this._findStumpParticlesByBase(firstWord)[0] + } + _findStumpParticlesByBase(firstWord) { + return this.topDownArray.filter(particle => particle.doesExtend("htmlTagParser") && particle.firstWord === firstWord) + } + hasLine(line) { + return this.getChildren().some(particle => particle.getLine() === line) + } + findStumpParticlesByChild(line) { + return this.topDownArray.filter(particle => particle.doesExtend("htmlTagParser") && particle.hasLine(line)) + } + findStumpParticlesWithClass(className) { + return this.topDownArray.filter( + particle => + particle.doesExtend("htmlTagParser") && + particle.has("class") && + particle + .getParticle("class") + .words + .includes(className) + ) + } + getShadowClass() { + return this.parent.getShadowClass() + } + // todo: should not be here + getStumpParticleParticleComponent() { + return this._particleComponent || this.parent.getStumpParticleParticleComponent() + } + // todo: should not be here + setStumpParticleParticleComponent(particleComponent) { + this._particleComponent = particleComponent + return this + } + getStumpParticleCss(prop) { + return this.getShadow().getShadowCss(prop) + } + getStumpParticleAttr(key) { + return this.get(key) + } + setStumpParticleAttr(key, value) { + // todo + return this + } + get asHtml() { + return this._toHtml() + } +errorParser + baseParser errorParser +componentDefinitionParser + extends htmlTagParser + pattern ^[a-zA-Z0-9_]+Component + cells componentTagNameCell + javascript + getTag() { + return "div" + } +htmlAttributeParser + javascript + _toHtml() { + return "" + } + getTextContent() {return ""} + getAttribute() { + return \` \${this.firstWord}="\${this.content}"\` + } + boolean isAttributeParser true + boolean isTileAttribute true + catchAllParser errorParser + catchAllCellType attributeValueCell + cells htmlAttributeNameCell +stumpExtendedAttributeNameCell + extends htmlAttributeNameCell + enum collapse blurCommand changeCommand clickCommand contextMenuCommand doubleClickCommand keyUpCommand lineClickCommand lineShiftClickCommand shiftClickCommand +stumpExtendedAttributeParser + description Parser types not present in HTML but included in stump. + extends htmlAttributeParser + cells stumpExtendedAttributeNameCell +lineOfHtmlContentParser + boolean isTileAttribute true + catchAllParser lineOfHtmlContentParser + catchAllCellType anyHtmlContentCell + javascript + getTextContent() {return this.getLine()} +bernParser + boolean isTileAttribute true + // todo Rename this particle type + description This is a particle where you can put any HTML content. It is called "bern" until someone comes up with a better name. + catchAllParser lineOfHtmlContentParser + javascript + _toHtml() { + return this.childrenToString() + } + getTextContent() {return ""} + cells bernKeywordCell`) + get handParsersProgram() { + return this.constructor.cachedHandParsersProgramRoot + } + static rootParser = stumpParser + } + + class blankLineParser extends ParserBackedParticle { + get emptyCell() { + return this.getWord(0) + } + _toHtml() { + return "" + } + getTextContent() { + return "" + } + } + + class htmlTagParser extends ParserBackedParticle { + createParserCombinator() { + return new Particle.ParserCombinator( + undefined, + Object.assign(Object.assign({}, super.createParserCombinator()._getFirstWordMapAsObject()), { + blockquote: htmlTagParser, + colgroup: htmlTagParser, + datalist: htmlTagParser, + fieldset: htmlTagParser, + menuitem: htmlTagParser, + noscript: htmlTagParser, + optgroup: htmlTagParser, + progress: htmlTagParser, + styleTag: htmlTagParser, + template: htmlTagParser, + textarea: htmlTagParser, + titleTag: htmlTagParser, + address: htmlTagParser, + article: htmlTagParser, + caption: htmlTagParser, + details: htmlTagParser, + section: htmlTagParser, + summary: htmlTagParser, + button: htmlTagParser, + canvas: htmlTagParser, + dialog: htmlTagParser, + figure: htmlTagParser, + footer: htmlTagParser, + header: htmlTagParser, + hgroup: htmlTagParser, + iframe: htmlTagParser, + keygen: htmlTagParser, + legend: htmlTagParser, + object: htmlTagParser, + option: htmlTagParser, + output: htmlTagParser, + script: htmlTagParser, + select: htmlTagParser, + source: htmlTagParser, + strong: htmlTagParser, + aside: htmlTagParser, + embed: htmlTagParser, + input: htmlTagParser, + label: htmlTagParser, + meter: htmlTagParser, + param: htmlTagParser, + small: htmlTagParser, + table: htmlTagParser, + tbody: htmlTagParser, + tfoot: htmlTagParser, + thead: htmlTagParser, + track: htmlTagParser, + video: htmlTagParser, + abbr: htmlTagParser, + area: htmlTagParser, + base: htmlTagParser, + body: htmlTagParser, + code: htmlTagParser, + form: htmlTagParser, + head: htmlTagParser, + html: htmlTagParser, + link: htmlTagParser, + main: htmlTagParser, + mark: htmlTagParser, + menu: htmlTagParser, + meta: htmlTagParser, + ruby: htmlTagParser, + samp: htmlTagParser, + span: htmlTagParser, + time: htmlTagParser, + bdi: htmlTagParser, + bdo: htmlTagParser, + col: htmlTagParser, + del: htmlTagParser, + dfn: htmlTagParser, + div: htmlTagParser, + img: htmlTagParser, + ins: htmlTagParser, + kbd: htmlTagParser, + map: htmlTagParser, + nav: htmlTagParser, + pre: htmlTagParser, + rtc: htmlTagParser, + sub: htmlTagParser, + sup: htmlTagParser, + var: htmlTagParser, + wbr: htmlTagParser, + br: htmlTagParser, + dd: htmlTagParser, + dl: htmlTagParser, + dt: htmlTagParser, + em: htmlTagParser, + h1: htmlTagParser, + h2: htmlTagParser, + h3: htmlTagParser, + h4: htmlTagParser, + h5: htmlTagParser, + h6: htmlTagParser, + hr: htmlTagParser, + li: htmlTagParser, + ol: htmlTagParser, + rb: htmlTagParser, + rp: htmlTagParser, + rt: htmlTagParser, + td: htmlTagParser, + th: htmlTagParser, + tr: htmlTagParser, + ul: htmlTagParser, + a: htmlTagParser, + b: htmlTagParser, + i: htmlTagParser, + p: htmlTagParser, + q: htmlTagParser, + s: htmlTagParser, + u: htmlTagParser, + oncanplaythrough: htmlAttributeParser, + ondurationchange: htmlAttributeParser, + onloadedmetadata: htmlAttributeParser, + contenteditable: htmlAttributeParser, + "accept-charset": htmlAttributeParser, + onbeforeunload: htmlAttributeParser, + onvolumechange: htmlAttributeParser, + onbeforeprint: htmlAttributeParser, + oncontextmenu: htmlAttributeParser, + autocomplete: htmlAttributeParser, + onafterprint: htmlAttributeParser, + onhashchange: htmlAttributeParser, + onloadeddata: htmlAttributeParser, + onmousewheel: htmlAttributeParser, + onratechange: htmlAttributeParser, + ontimeupdate: htmlAttributeParser, + oncuechange: htmlAttributeParser, + ondragenter: htmlAttributeParser, + ondragleave: htmlAttributeParser, + ondragstart: htmlAttributeParser, + onloadstart: htmlAttributeParser, + onmousedown: htmlAttributeParser, + onmousemove: htmlAttributeParser, + onmouseover: htmlAttributeParser, + placeholder: htmlAttributeParser, + formaction: htmlAttributeParser, + "http-equiv": htmlAttributeParser, + novalidate: htmlAttributeParser, + ondblclick: htmlAttributeParser, + ondragover: htmlAttributeParser, + onkeypress: htmlAttributeParser, + onmouseout: htmlAttributeParser, + onpagehide: htmlAttributeParser, + onpageshow: htmlAttributeParser, + onpopstate: htmlAttributeParser, + onprogress: htmlAttributeParser, + spellcheck: htmlAttributeParser, + accesskey: htmlAttributeParser, + autofocus: htmlAttributeParser, + draggable: htmlAttributeParser, + maxlength: htmlAttributeParser, + oncanplay: htmlAttributeParser, + ondragend: htmlAttributeParser, + onemptied: htmlAttributeParser, + oninvalid: htmlAttributeParser, + onkeydown: htmlAttributeParser, + onmouseup: htmlAttributeParser, + onoffline: htmlAttributeParser, + onplaying: htmlAttributeParser, + onseeking: htmlAttributeParser, + onstalled: htmlAttributeParser, + onstorage: htmlAttributeParser, + onsuspend: htmlAttributeParser, + onwaiting: htmlAttributeParser, + translate: htmlAttributeParser, + autoplay: htmlAttributeParser, + controls: htmlAttributeParser, + datetime: htmlAttributeParser, + disabled: htmlAttributeParser, + download: htmlAttributeParser, + dropzone: htmlAttributeParser, + hreflang: htmlAttributeParser, + multiple: htmlAttributeParser, + onchange: htmlAttributeParser, + ononline: htmlAttributeParser, + onresize: htmlAttributeParser, + onscroll: htmlAttributeParser, + onsearch: htmlAttributeParser, + onseeked: htmlAttributeParser, + onselect: htmlAttributeParser, + onsubmit: htmlAttributeParser, + ontoggle: htmlAttributeParser, + onunload: htmlAttributeParser, + property: htmlAttributeParser, + readonly: htmlAttributeParser, + required: htmlAttributeParser, + reversed: htmlAttributeParser, + selected: htmlAttributeParser, + tabindex: htmlAttributeParser, + bgcolor: htmlAttributeParser, + charset: htmlAttributeParser, + checked: htmlAttributeParser, + colspan: htmlAttributeParser, + content: htmlAttributeParser, + default: htmlAttributeParser, + dirname: htmlAttributeParser, + enctype: htmlAttributeParser, + headers: htmlAttributeParser, + onabort: htmlAttributeParser, + onclick: htmlAttributeParser, + onended: htmlAttributeParser, + onerror: htmlAttributeParser, + onfocus: htmlAttributeParser, + oninput: htmlAttributeParser, + onkeyup: htmlAttributeParser, + onpaste: htmlAttributeParser, + onpause: htmlAttributeParser, + onreset: htmlAttributeParser, + onwheel: htmlAttributeParser, + optimum: htmlAttributeParser, + pattern: htmlAttributeParser, + preload: htmlAttributeParser, + rowspan: htmlAttributeParser, + sandbox: htmlAttributeParser, + srclang: htmlAttributeParser, + accept: htmlAttributeParser, + action: htmlAttributeParser, + border: htmlAttributeParser, + coords: htmlAttributeParser, + height: htmlAttributeParser, + hidden: htmlAttributeParser, + method: htmlAttributeParser, + onblur: htmlAttributeParser, + oncopy: htmlAttributeParser, + ondrag: htmlAttributeParser, + ondrop: htmlAttributeParser, + onload: htmlAttributeParser, + onplay: htmlAttributeParser, + poster: htmlAttributeParser, + srcdoc: htmlAttributeParser, + srcset: htmlAttributeParser, + target: htmlAttributeParser, + usemap: htmlAttributeParser, + align: htmlAttributeParser, + async: htmlAttributeParser, + class: htmlAttributeParser, + color: htmlAttributeParser, + defer: htmlAttributeParser, + ismap: htmlAttributeParser, + media: htmlAttributeParser, + muted: htmlAttributeParser, + oncut: htmlAttributeParser, + scope: htmlAttributeParser, + shape: htmlAttributeParser, + sizes: htmlAttributeParser, + start: htmlAttributeParser, + style: htmlAttributeParser, + title: htmlAttributeParser, + value: htmlAttributeParser, + width: htmlAttributeParser, + cols: htmlAttributeParser, + high: htmlAttributeParser, + href: htmlAttributeParser, + kind: htmlAttributeParser, + lang: htmlAttributeParser, + list: htmlAttributeParser, + loop: htmlAttributeParser, + name: htmlAttributeParser, + open: htmlAttributeParser, + rows: htmlAttributeParser, + size: htmlAttributeParser, + step: htmlAttributeParser, + type: htmlAttributeParser, + wrap: htmlAttributeParser, + alt: htmlAttributeParser, + dir: htmlAttributeParser, + for: htmlAttributeParser, + low: htmlAttributeParser, + max: htmlAttributeParser, + min: htmlAttributeParser, + rel: htmlAttributeParser, + src: htmlAttributeParser, + id: htmlAttributeParser, + lineShiftClickCommand: stumpExtendedAttributeParser, + contextMenuCommand: stumpExtendedAttributeParser, + doubleClickCommand: stumpExtendedAttributeParser, + shiftClickCommand: stumpExtendedAttributeParser, + lineClickCommand: stumpExtendedAttributeParser, + changeCommand: stumpExtendedAttributeParser, + clickCommand: stumpExtendedAttributeParser, + keyUpCommand: stumpExtendedAttributeParser, + blurCommand: stumpExtendedAttributeParser, + collapse: stumpExtendedAttributeParser, + bern: bernParser + }), + [ + { regex: /^$/, parser: blankLineParser }, + { regex: /^[a-zA-Z0-9_]+Component/, parser: componentDefinitionParser } + ] + ) + } + get htmlTagNameCell() { + return this.getWord(0) + } + get anyHtmlContentCell() { + return this.getWordsFrom(1) + } + isHtmlTagParser = true + getTag() { + // we need to remove the "Tag" bit to handle the style and title attribute/tag conflict. + const firstWord = this.firstWord + const map = { + titleTag: "title", + styleTag: "style" + } + return map[firstWord] || firstWord + } + _getHtmlJoinByCharacter() { + return "" + } + asHtmlWithSuids() { + return this._toHtml(undefined, true) + } + _getOneLiner() { + const oneLinerWords = this.getWordsFrom(1) + return oneLinerWords.length ? oneLinerWords.join(" ") : "" + } + getTextContent() { + return this._getOneLiner() + } + shouldCollapse() { + return this.has("collapse") + } + get domElement() { + var elem = document.createElement(this.getTag()) + elem.setAttribute("stumpUid", this._getUid()) + this.filter(particle => particle.isAttributeParser).forEach(child => elem.setAttribute(child.firstWord, child.content)) + elem.innerHTML = this.has("bern") ? this.getParticle("bern").childrenToString() : this._getOneLiner() + this.filter(particle => particle.isHtmlTagParser).forEach(child => elem.appendChild(child.domElement)) + return elem + } + _toHtml(indentCount, withSuid) { + const tag = this.getTag() + const children = this.map(child => child._toHtml(indentCount + 1, withSuid)).join("") + const attributesStr = this.filter(particle => particle.isAttributeParser) + .map(child => child.getAttribute()) + .join("") + const indent = " ".repeat(indentCount) + const collapse = this.shouldCollapse() + const indentForChildParsers = !collapse && this.getChildInstancesOfParserId("htmlTagParser").length > 0 + const suid = withSuid ? ` stumpUid="${this._getUid()}"` : "" + const oneLiner = this._getOneLiner() + return `${!collapse ? indent : ""}<${tag}${attributesStr}${suid}>${oneLiner}${indentForChildParsers ? "\n" : ""}${children}${collapse ? "" : "\n"}` + } + removeCssStumpParticle() { + return this.removeStumpParticle() + } + removeStumpParticle() { + this.getShadow().removeShadow() + return this.destroy() + } + getParticleByGuid(guid) { + return this.topDownArray.find(particle => particle._getUid() === guid) + } + addClassToStumpParticle(className) { + const classParser = this.touchParticle("class") + const words = classParser.getWordsFrom(1) + // note: we call add on shadow regardless, because at the moment stump may have gotten out of + // sync with shadow, if things modified the dom. todo: cleanup. + this.getShadow().addClassToShadow(className) + if (words.includes(className)) return this + words.push(className) + classParser.setContent(words.join(this.wordBreakSymbol)) + return this + } + removeClassFromStumpParticle(className) { + const classParser = this.getParticle("class") + if (!classParser) return this + const newClasses = classParser.words.filter(word => word !== className) + if (!newClasses.length) classParser.destroy() + else classParser.setContent(newClasses.join(" ")) + this.getShadow().removeClassFromShadow(className) + return this + } + stumpParticleHasClass(className) { + const classParser = this.getParticle("class") + return classParser && classParser.words.includes(className) ? true : false + } + isStumpParticleCheckbox() { + return this.get("type") === "checkbox" + } + getShadow() { + if (!this._shadow) { + const shadowClass = this.getShadowClass() + this._shadow = new shadowClass(this) + } + return this._shadow + } + insertCssChildParticle(text, index) { + return this.insertChildParticle(text, index) + } + insertChildParticle(text, index) { + const singleParticle = new Particle(text).getChildren()[0] + const newParticle = this.insertLineAndChildren(singleParticle.getLine(), singleParticle.childrenToString(), index) + const stumpParserIndex = this.filter(particle => particle.isHtmlTagParser).indexOf(newParticle) + this.getShadow().insertHtmlParticle(newParticle, stumpParserIndex) + return newParticle + } + isInputType() { + return ["input", "textarea"].includes(this.getTag()) || this.get("contenteditable") === "true" + } + findStumpParticleByChild(line) { + return this.findStumpParticlesByChild(line)[0] + } + findStumpParticleByChildString(line) { + return this.topDownArray.find(particle => + particle + .map(child => child.getLine()) + .join("\n") + .includes(line) + ) + } + findStumpParticleByFirstWord(firstWord) { + return this._findStumpParticlesByBase(firstWord)[0] + } + _findStumpParticlesByBase(firstWord) { + return this.topDownArray.filter(particle => particle.doesExtend("htmlTagParser") && particle.firstWord === firstWord) + } + hasLine(line) { + return this.getChildren().some(particle => particle.getLine() === line) + } + findStumpParticlesByChild(line) { + return this.topDownArray.filter(particle => particle.doesExtend("htmlTagParser") && particle.hasLine(line)) + } + findStumpParticlesWithClass(className) { + return this.topDownArray.filter(particle => particle.doesExtend("htmlTagParser") && particle.has("class") && particle.getParticle("class").words.includes(className)) + } + getShadowClass() { + return this.parent.getShadowClass() + } + // todo: should not be here + getStumpParticleParticleComponent() { + return this._particleComponent || this.parent.getStumpParticleParticleComponent() + } + // todo: should not be here + setStumpParticleParticleComponent(particleComponent) { + this._particleComponent = particleComponent + return this + } + getStumpParticleCss(prop) { + return this.getShadow().getShadowCss(prop) + } + getStumpParticleAttr(key) { + return this.get(key) + } + setStumpParticleAttr(key, value) { + // todo + return this + } + get asHtml() { + return this._toHtml() + } + } + + class errorParser extends ParserBackedParticle { + getErrors() { + return this._getErrorParserErrors() + } + } + + class componentDefinitionParser extends htmlTagParser { + get componentTagNameCell() { + return this.getWord(0) + } + getTag() { + return "div" + } + } + + class htmlAttributeParser extends ParserBackedParticle { + createParserCombinator() { + return new Particle.ParserCombinator(errorParser, undefined, undefined) + } + get htmlAttributeNameCell() { + return this.getWord(0) + } + get attributeValueCell() { + return this.getWordsFrom(1) + } + get isTileAttribute() { + return true + } + get isAttributeParser() { + return true + } + _toHtml() { + return "" + } + getTextContent() { + return "" + } + getAttribute() { + return ` ${this.firstWord}="${this.content}"` + } + } + + class stumpExtendedAttributeParser extends htmlAttributeParser { + get stumpExtendedAttributeNameCell() { + return this.getWord(0) + } + } + + class lineOfHtmlContentParser extends ParserBackedParticle { + createParserCombinator() { + return new Particle.ParserCombinator(lineOfHtmlContentParser, undefined, undefined) + } + get anyHtmlContentCell() { + return this.getWordsFrom(0) + } + get isTileAttribute() { + return true + } + getTextContent() { + return this.getLine() + } + } + + class bernParser extends ParserBackedParticle { + createParserCombinator() { + return new Particle.ParserCombinator(lineOfHtmlContentParser, undefined, undefined) + } + get bernKeywordCell() { + return this.getWord(0) + } + get isTileAttribute() { + return true + } + _toHtml() { + return this.childrenToString() + } + getTextContent() { + return "" + } + } + + window.stumpParser = stumpParser +} + + +{ + class hakonParser extends ParserBackedParticle { + createParserCombinator() { + return new Particle.ParserCombinator(selectorParser, Object.assign(Object.assign({}, super.createParserCombinator()._getFirstWordMapAsObject()), { comment: commentParser }), undefined) + } + getSelector() { + return "" + } + compile() { + return this.topDownArray + .filter(particle => particle.isSelectorParser) + .map(child => child.compile()) + .join("") + } + static cachedHandParsersProgramRoot = new HandParsersProgram(`// Cell Parsers +anyCell +keywordCell +commentKeywordCell + extends keywordCell + paint comment + enum comment +extraCell + paint invalid +cssValueCell + paint constant.numeric +selectorCell + paint keyword.control + examples body h1 + // todo add html tags, css and ids selector regexes, etc +vendorPrefixPropertyKeywordCell + description Properties like -moz-column-fill + paint variable.function + extends keywordCell +propertyKeywordCell + paint variable.function + extends keywordCell + // todo Where are these coming from? Can we add a url link + enum align-content align-items align-self all animation animation-delay animation-direction animation-duration animation-fill-mode animation-iteration-count animation-name animation-play-state animation-timing-function backface-visibility background background-attachment background-blend-mode background-clip background-color background-image background-origin background-position background-repeat background-size border border-bottom border-bottom-color border-bottom-left-radius border-bottom-right-radius border-bottom-style border-bottom-width border-collapse border-color border-image border-image-outset border-image-repeat border-image-slice border-image-source border-image-width border-left border-left-color border-left-style border-left-width border-radius border-right border-right-color border-right-style border-right-width border-spacing border-style border-top border-top-color border-top-left-radius border-top-right-radius border-top-style border-top-width border-width bottom box-shadow box-sizing break-inside caption-side clear clip color column-count column-fill column-gap column-rule column-rule-color column-rule-style column-rule-width column-span column-width columns content counter-increment counter-reset cursor direction display empty-cells fill filter flex flex-basis flex-direction flex-flow flex-grow flex-shrink flex-wrap float font @font-face font-family font-size font-size-adjust font-stretch font-style font-variant font-weight hanging-punctuation height hyphens justify-content @keyframes left letter-spacing line-height list-style list-style-image list-style-position list-style-type margin margin-bottom margin-left margin-right margin-top max-height max-width @media min-height min-width nav-down nav-index nav-left nav-right nav-up opacity order outline outline-color outline-offset outline-style outline-width overflow overflow-x overflow-y padding padding-bottom padding-left padding-right padding-top page-break-after page-break-before page-break-inside perspective perspective-origin position quotes resize right tab-size table-layout text-align text-align-last text-decoration text-decoration-color text-decoration-line text-decoration-style text-indent text-justify text-overflow text-shadow text-transform top transform transform-origin transform-style transition transition-delay transition-duration transition-property transition-timing-function unicode-bidi vertical-align visibility white-space width word-break word-spacing word-wrap z-index overscroll-behavior-x user-select -ms-touch-action -webkit-user-select -webkit-touch-callout -moz-user-select touch-action -ms-user-select -khtml-user-select gap grid-auto-flow grid-column grid-column-end grid-column-gap grid-column-start grid-gap grid-row grid-row-end grid-row-gap grid-row-start grid-template-columns grid-template-rows justify-items justify-self +errorCell + paint invalid +commentCell + paint comment + +// Line Parsers +hakonParser + root + // todo Add variables? + description A prefix Language that compiles to CSS + compilesTo css + inScope commentParser + catchAllParser selectorParser + javascript + getSelector() { + return "" + } + compile() { + return this.topDownArray + .filter(particle => particle.isSelectorParser) + .map(child => child.compile()) + .join("") + } + example A basic example + body + font-size 12px + h1,h2 + color red + a + &:hover + color blue + font-size 17px +propertyParser + catchAllCellType cssValueCell + catchAllParser errorParser + javascript + compile(spaces) { + return \`\${spaces}\${this.firstWord}: \${this.content};\` + } + cells propertyKeywordCell +variableParser + extends propertyParser + pattern -- +browserPrefixPropertyParser + extends propertyParser + pattern ^\\-\\w.+ + cells vendorPrefixPropertyKeywordCell +errorParser + catchAllParser errorParser + catchAllCellType errorCell + baseParser errorParser +commentParser + cells commentKeywordCell + catchAllCellType commentCell + catchAllParser commentParser +selectorParser + inScope propertyParser variableParser commentParser + catchAllParser selectorParser + boolean isSelectorParser true + javascript + getSelector() { + const parentSelector = this.parent.getSelector() + return this.firstWord + .split(",") + .map(part => { + if (part.startsWith("&")) return parentSelector + part.substr(1) + return parentSelector ? parentSelector + " " + part : part + }) + .join(",") + } + compile() { + const propertyParsers = this.getChildren().filter(particle => particle.doesExtend("propertyParser")) + if (!propertyParsers.length) return "" + const spaces = " " + return \`\${this.getSelector()} { + \${propertyParsers.map(child => child.compile(spaces)).join("\\n")} + }\\n\` + } + cells selectorCell`) + get handParsersProgram() { + return this.constructor.cachedHandParsersProgramRoot + } + static rootParser = hakonParser + } + + class propertyParser extends ParserBackedParticle { + createParserCombinator() { + return new Particle.ParserCombinator(errorParser, undefined, undefined) + } + get propertyKeywordCell() { + return this.getWord(0) + } + get cssValueCell() { + return this.getWordsFrom(1) + } + compile(spaces) { + return `${spaces}${this.firstWord}: ${this.content};` + } + } + + class variableParser extends propertyParser {} + + class browserPrefixPropertyParser extends propertyParser { + get vendorPrefixPropertyKeywordCell() { + return this.getWord(0) + } + } + + class errorParser extends ParserBackedParticle { + createParserCombinator() { + return new Particle.ParserCombinator(errorParser, undefined, undefined) + } + getErrors() { + return this._getErrorParserErrors() + } + get errorCell() { + return this.getWordsFrom(0) + } + } + + class commentParser extends ParserBackedParticle { + createParserCombinator() { + return new Particle.ParserCombinator(commentParser, undefined, undefined) + } + get commentKeywordCell() { + return this.getWord(0) + } + get commentCell() { + return this.getWordsFrom(1) + } + } + + class selectorParser extends ParserBackedParticle { + createParserCombinator() { + return new Particle.ParserCombinator( + selectorParser, + Object.assign(Object.assign({}, super.createParserCombinator()._getFirstWordMapAsObject()), { + "border-bottom-right-radius": propertyParser, + "transition-timing-function": propertyParser, + "animation-iteration-count": propertyParser, + "animation-timing-function": propertyParser, + "border-bottom-left-radius": propertyParser, + "border-top-right-radius": propertyParser, + "border-top-left-radius": propertyParser, + "background-attachment": propertyParser, + "background-blend-mode": propertyParser, + "text-decoration-color": propertyParser, + "text-decoration-style": propertyParser, + "overscroll-behavior-x": propertyParser, + "-webkit-touch-callout": propertyParser, + "grid-template-columns": propertyParser, + "animation-play-state": propertyParser, + "text-decoration-line": propertyParser, + "animation-direction": propertyParser, + "animation-fill-mode": propertyParser, + "backface-visibility": propertyParser, + "background-position": propertyParser, + "border-bottom-color": propertyParser, + "border-bottom-style": propertyParser, + "border-bottom-width": propertyParser, + "border-image-outset": propertyParser, + "border-image-repeat": propertyParser, + "border-image-source": propertyParser, + "hanging-punctuation": propertyParser, + "list-style-position": propertyParser, + "transition-duration": propertyParser, + "transition-property": propertyParser, + "-webkit-user-select": propertyParser, + "animation-duration": propertyParser, + "border-image-slice": propertyParser, + "border-image-width": propertyParser, + "border-right-color": propertyParser, + "border-right-style": propertyParser, + "border-right-width": propertyParser, + "perspective-origin": propertyParser, + "-khtml-user-select": propertyParser, + "grid-template-rows": propertyParser, + "background-origin": propertyParser, + "background-repeat": propertyParser, + "border-left-color": propertyParser, + "border-left-style": propertyParser, + "border-left-width": propertyParser, + "column-rule-color": propertyParser, + "column-rule-style": propertyParser, + "column-rule-width": propertyParser, + "counter-increment": propertyParser, + "page-break-before": propertyParser, + "page-break-inside": propertyParser, + "grid-column-start": propertyParser, + "background-color": propertyParser, + "background-image": propertyParser, + "border-top-color": propertyParser, + "border-top-style": propertyParser, + "border-top-width": propertyParser, + "font-size-adjust": propertyParser, + "list-style-image": propertyParser, + "page-break-after": propertyParser, + "transform-origin": propertyParser, + "transition-delay": propertyParser, + "-ms-touch-action": propertyParser, + "-moz-user-select": propertyParser, + "animation-delay": propertyParser, + "background-clip": propertyParser, + "background-size": propertyParser, + "border-collapse": propertyParser, + "justify-content": propertyParser, + "list-style-type": propertyParser, + "text-align-last": propertyParser, + "text-decoration": propertyParser, + "transform-style": propertyParser, + "-ms-user-select": propertyParser, + "grid-column-end": propertyParser, + "grid-column-gap": propertyParser, + "animation-name": propertyParser, + "border-spacing": propertyParser, + "flex-direction": propertyParser, + "letter-spacing": propertyParser, + "outline-offset": propertyParser, + "padding-bottom": propertyParser, + "text-transform": propertyParser, + "vertical-align": propertyParser, + "grid-auto-flow": propertyParser, + "grid-row-start": propertyParser, + "align-content": propertyParser, + "border-bottom": propertyParser, + "border-radius": propertyParser, + "counter-reset": propertyParser, + "margin-bottom": propertyParser, + "outline-color": propertyParser, + "outline-style": propertyParser, + "outline-width": propertyParser, + "padding-right": propertyParser, + "text-overflow": propertyParser, + "justify-items": propertyParser, + "border-color": propertyParser, + "border-image": propertyParser, + "border-right": propertyParser, + "border-style": propertyParser, + "border-width": propertyParser, + "break-inside": propertyParser, + "caption-side": propertyParser, + "column-count": propertyParser, + "column-width": propertyParser, + "font-stretch": propertyParser, + "font-variant": propertyParser, + "margin-right": propertyParser, + "padding-left": propertyParser, + "table-layout": propertyParser, + "text-justify": propertyParser, + "unicode-bidi": propertyParser, + "word-spacing": propertyParser, + "touch-action": propertyParser, + "grid-row-end": propertyParser, + "grid-row-gap": propertyParser, + "justify-self": propertyParser, + "align-items": propertyParser, + "border-left": propertyParser, + "column-fill": propertyParser, + "column-rule": propertyParser, + "column-span": propertyParser, + "empty-cells": propertyParser, + "flex-shrink": propertyParser, + "font-family": propertyParser, + "font-weight": propertyParser, + "line-height": propertyParser, + "margin-left": propertyParser, + "padding-top": propertyParser, + perspective: propertyParser, + "text-indent": propertyParser, + "text-shadow": propertyParser, + "white-space": propertyParser, + "user-select": propertyParser, + "grid-column": propertyParser, + "align-self": propertyParser, + background: propertyParser, + "border-top": propertyParser, + "box-shadow": propertyParser, + "box-sizing": propertyParser, + "column-gap": propertyParser, + "flex-basis": propertyParser, + "@font-face": propertyParser, + "font-style": propertyParser, + "@keyframes": propertyParser, + "list-style": propertyParser, + "margin-top": propertyParser, + "max-height": propertyParser, + "min-height": propertyParser, + "overflow-x": propertyParser, + "overflow-y": propertyParser, + "text-align": propertyParser, + transition: propertyParser, + visibility: propertyParser, + "word-break": propertyParser, + animation: propertyParser, + direction: propertyParser, + "flex-flow": propertyParser, + "flex-grow": propertyParser, + "flex-wrap": propertyParser, + "font-size": propertyParser, + "max-width": propertyParser, + "min-width": propertyParser, + "nav-index": propertyParser, + "nav-right": propertyParser, + transform: propertyParser, + "word-wrap": propertyParser, + "nav-down": propertyParser, + "nav-left": propertyParser, + overflow: propertyParser, + position: propertyParser, + "tab-size": propertyParser, + "grid-gap": propertyParser, + "grid-row": propertyParser, + columns: propertyParser, + content: propertyParser, + display: propertyParser, + hyphens: propertyParser, + opacity: propertyParser, + outline: propertyParser, + padding: propertyParser, + "z-index": propertyParser, + border: propertyParser, + bottom: propertyParser, + cursor: propertyParser, + filter: propertyParser, + height: propertyParser, + margin: propertyParser, + "@media": propertyParser, + "nav-up": propertyParser, + quotes: propertyParser, + resize: propertyParser, + clear: propertyParser, + color: propertyParser, + float: propertyParser, + order: propertyParser, + right: propertyParser, + width: propertyParser, + clip: propertyParser, + fill: propertyParser, + flex: propertyParser, + font: propertyParser, + left: propertyParser, + all: propertyParser, + top: propertyParser, + gap: propertyParser, + "": propertyParser, + comment: commentParser + }), + [ + { regex: /--/, parser: variableParser }, + { regex: /^\-\w.+/, parser: browserPrefixPropertyParser } + ] + ) + } + get selectorCell() { + return this.getWord(0) + } + get isSelectorParser() { + return true + } + getSelector() { + const parentSelector = this.parent.getSelector() + return this.firstWord + .split(",") + .map(part => { + if (part.startsWith("&")) return parentSelector + part.substr(1) + return parentSelector ? parentSelector + " " + part : part + }) + .join(",") + } + compile() { + const propertyParsers = this.getChildren().filter(particle => particle.doesExtend("propertyParser")) + if (!propertyParsers.length) return "" + const spaces = " " + return `${this.getSelector()} { +${propertyParsers.map(child => child.compile(spaces)).join("\n")} +}\n` + } + } + + window.hakonParser = hakonParser +} + + +//onsave scrollsdk build produce ParticleComponentFramework.browser.js +const BrowserEvents = {} +BrowserEvents.click = "click" +BrowserEvents.change = "change" +BrowserEvents.mouseover = "mouseover" +BrowserEvents.mouseout = "mouseout" +BrowserEvents.mousedown = "mousedown" +BrowserEvents.contextmenu = "contextmenu" +BrowserEvents.keypress = "keypress" +BrowserEvents.keyup = "keyup" +BrowserEvents.focus = "focus" +BrowserEvents.mousemove = "mousemove" +BrowserEvents.dblclick = "dblclick" +BrowserEvents.submit = "submit" +BrowserEvents.blur = "blur" +BrowserEvents.paste = "paste" +BrowserEvents.copy = "copy" +BrowserEvents.resize = "resize" +BrowserEvents.cut = "cut" +BrowserEvents.drop = "drop" +BrowserEvents.dragover = "dragover" +BrowserEvents.dragenter = "dragenter" +BrowserEvents.dragleave = "dragleave" +BrowserEvents.ready = "ready" +const WillowConstants = {} +// todo: cleanup +WillowConstants.clickCommand = "clickCommand" +WillowConstants.shiftClickCommand = "shiftClickCommand" +WillowConstants.blurCommand = "blurCommand" +WillowConstants.keyUpCommand = "keyUpCommand" +WillowConstants.contextMenuCommand = "contextMenuCommand" +WillowConstants.changeCommand = "changeCommand" +WillowConstants.doubleClickCommand = "doubleClickCommand" +// todo: cleanup +WillowConstants.titleTag = "titleTag" +WillowConstants.styleTag = "styleTag" +WillowConstants.tagMap = {} +WillowConstants.tagMap[WillowConstants.styleTag] = "style" +WillowConstants.tagMap[WillowConstants.titleTag] = "title" +WillowConstants.tags = {} +WillowConstants.tags.html = "html" +WillowConstants.tags.head = "head" +WillowConstants.tags.body = "body" +WillowConstants.collapse = "collapse" +WillowConstants.uidAttribute = "stumpUid" +WillowConstants.class = "class" +WillowConstants.type = "type" +WillowConstants.value = "value" +WillowConstants.name = "name" +WillowConstants.checkbox = "checkbox" +WillowConstants.checkedSelector = ":checked" +WillowConstants.contenteditable = "contenteditable" +WillowConstants.inputTypes = ["input", "textarea"] +var CacheType +;(function (CacheType) { + CacheType["inBrowserMemory"] = "inBrowserMemory" +})(CacheType || (CacheType = {})) +class WillowHTTPResponse { + constructor(superAgentResponse) { + this._cacheType = CacheType.inBrowserMemory + this._fromCache = false + this._cacheTime = Date.now() + this._superAgentResponse = superAgentResponse + this._mimeType = superAgentResponse && superAgentResponse.type + } + // todo: ServerMemoryCacheTime and ServerMemoryDiskCacheTime + get cacheTime() { + return this._cacheTime + } + get cacheType() { + return this._cacheType + } + get body() { + return this._superAgentResponse && this._superAgentResponse.body + } + get text() { + if (this._text === undefined) this._text = this._superAgentResponse && this._superAgentResponse.text ? this._superAgentResponse.text : this.body ? JSON.stringify(this.body, null, 2) : "" + return this._text + } + get asJson() { + return this.body ? this.body : JSON.parse(this.text) + } + get fromCache() { + return this._fromCache + } + setFromCache(val) { + this._fromCache = val + return this + } + getParsedDataOrText() { + if (this._mimeType === "text/csv") return this.text + return this.body || this.text + } +} +class WillowHTTPProxyCacheResponse extends WillowHTTPResponse { + constructor(proxyServerResponse) { + super() + this._proxyServerResponse = proxyServerResponse + this._cacheType = proxyServerResponse.body.cacheType + this._cacheTime = proxyServerResponse.body.cacheTime + this._text = proxyServerResponse.body.text + } +} +class AbstractWillowShadow { + constructor(stumpParticle) { + this._stumpParticle = stumpParticle + } + getShadowStumpParticle() { + return this._stumpParticle + } + getShadowValue() { + return this._val + } + removeShadow() { + return this + } + setInputOrTextAreaValue(value) { + this._val = value + return this + } + getShadowParent() { + return this.getShadowStumpParticle().parent.getShadow() + } + getPositionAndDimensions(gridSize = 1) { + const offset = this.getShadowOffset() + const parentOffset = this.getShadowParent().getShadowOffset() + return { + left: Math.floor((offset.left - parentOffset.left) / gridSize), + top: Math.floor((offset.top - parentOffset.top) / gridSize), + width: Math.floor(this.getShadowWidth() / gridSize), + height: Math.floor(this.getShadowHeight() / gridSize) + } + } + shadowHasClass(name) { + return false + } + getShadowAttr(name) { + return "" + } + makeResizable(options) { + return this + } + makeDraggable(options) { + return this + } + makeSelectable(options) { + return this + } + isShadowChecked() { + return false + } + getShadowOffset() { + return { left: 111, top: 111 } + } + getShadowWidth() { + return 111 + } + getShadowHeight() { + return 111 + } + setShadowAttr(name, value) { + return this + } + isShadowDraggable() { + return this.shadowHasClass("draggable") + } + toggleShadow() {} + addClassToShadow(className) {} + removeClassFromShadow(className) { + return this + } + onShadowEvent(event, fn) { + // todo: + return this + } + onShadowEventWithSelector(event, selector, fn) { + // todo: + return this + } + offShadowEvent(event, fn) { + // todo: + return this + } + triggerShadowEvent(name) { + return this + } + getShadowPosition() { + return { + left: 111, + top: 111 + } + } + getShadowOuterHeight() { + return 11 + } + getShadowOuterWidth() { + return 11 + } + getShadowCss(property) { + return "" + } + insertHtmlParticle(childParticle, index) {} + get element() { + return {} + } +} +class WillowShadow extends AbstractWillowShadow {} +class WillowStore { + constructor() { + this._values = {} + } + get(key) { + return this._values[key] + } + set(key, value) { + this._values[key] = value + return this + } + remove(key) { + delete this._values[key] + } + each(fn) { + Object.keys(this._values).forEach(key => { + fn(this._values[key], key) + }) + } + clearAll() { + this._values = {} + } +} +class WillowMousetrap { + constructor() { + this.prototype = {} + } + bind() {} +} +// this one should have no document, window, $, et cetera. +class AbstractWillowBrowser extends stumpParser { + constructor(fullHtmlPageUrlIncludingProtocolAndFileName) { + super(`${WillowConstants.tags.html} + ${WillowConstants.tags.head} + ${WillowConstants.tags.body}`) + this._offlineMode = false + this._httpGetResponseCache = {} + this.location = {} + this._htmlStumpParticle = this.particleAt(0) + this._headStumpParticle = this.particleAt(0).particleAt(0) + this._bodyStumpParticle = this.particleAt(0).particleAt(1) + this.addSuidsToHtmlHeadAndBodyShadows() + this._fullHtmlPageUrlIncludingProtocolAndFileName = fullHtmlPageUrlIncludingProtocolAndFileName + const url = new URL(fullHtmlPageUrlIncludingProtocolAndFileName) + this.location.port = url.port + this.location.protocol = url.protocol + this.location.hostname = url.hostname + this.location.host = url.host + } + _getPort() { + return this.location.port ? ":" + this.location.port : "" + } + getHash() { + return this.location.hash || "" + } + setHash(value) { + this.location.hash = value + } + setHtmlOfElementWithIdHack(id, html) {} + setHtmlOfElementsWithClassHack(id, html) {} + setValueOfElementWithIdHack(id, value) {} + setValueOfElementWithClassHack(id, value) {} + getElementById(id) {} + queryObjectToQueryString(obj) { + const params = new URLSearchParams() + for (const [key, value] of Object.entries(obj)) { + params.set(key, String(value)) + } + return params.toString() + } + toPrettyDeepLink(particleCode, queryObject) { + // todo: move things to a constant. + const particleBreakSymbol = "~" + const edgeSymbol = "_" + const obj = Object.assign({}, queryObject) + if (!particleCode.includes(particleBreakSymbol) && !particleCode.includes(edgeSymbol)) { + obj.particleBreakSymbol = particleBreakSymbol + obj.edgeSymbol = edgeSymbol + obj.data = encodeURIComponent(particleCode.replace(/ /g, edgeSymbol).replace(/\n/g, particleBreakSymbol)) + } else obj.data = encodeURIComponent(particleCode) + return this.getAppWebPageUrl() + "?" + this.queryObjectToQueryString(obj) + } + getHost() { + return this.location.host + } + reload() {} + toggleOfflineMode() { + this._offlineMode = !this._offlineMode + } + addSuidsToHtmlHeadAndBodyShadows() {} + getShadowClass() { + return WillowShadow + } + getMockMouseEvent() { + return { + clientX: 0, + clientY: 0, + offsetX: 0, + offsetY: 0 + } + } + toggleFullScreen() {} + getMousetrap() { + if (!this._mousetrap) this._mousetrap = new WillowMousetrap() + return this._mousetrap + } + _getFocusedShadow() { + return this._focusedShadow || this.getBodyStumpParticle().getShadow() + } + getHeadStumpParticle() { + return this._headStumpParticle + } + getBodyStumpParticle() { + return this._bodyStumpParticle + } + getHtmlStumpParticle() { + return this._htmlStumpParticle + } + getStore() { + if (!this._store) this._store = new WillowStore() + return this._store + } + someInputHasFocus() { + const focusedShadow = this._getFocusedShadow() + if (!focusedShadow) return false + const stumpParticle = focusedShadow.getShadowStumpParticle() + return stumpParticle && stumpParticle.isInputType() + } + copyTextToClipboard(text) {} + setCopyData(evt, str) {} + getAppWebPageUrl() { + return this._fullHtmlPageUrlIncludingProtocolAndFileName + } + getAppWebPageParentFolderWithoutTrailingSlash() { + return Utils.getPathWithoutFileName(this._fullHtmlPageUrlIncludingProtocolAndFileName) + } + _makeRelativeUrlAbsolute(url) { + if (url.startsWith("http://") || url.startsWith("https://")) return url + return this.getAppWebPageParentFolderWithoutTrailingSlash() + "/" + url.replace(/^\//, "") + } + async makeUrlAbsoluteAndHttpGetUrl(url, queryStringObject, responseClass = WillowHTTPResponse) { + return this.httpGetUrl(this._makeRelativeUrlAbsolute(url), queryStringObject, responseClass) + } + async httpGetUrl(url, queryStringObject, responseClass = WillowHTTPResponse) { + if (this._offlineMode) return new WillowHTTPResponse() + const superAgentResponse = await superagent + .get(url) + .query(queryStringObject) + .set(this._headers || {}) + return new responseClass(superAgentResponse) + } + _getFromResponseCache(cacheKey) { + const hit = this._httpGetResponseCache[cacheKey] + if (hit) hit.setFromCache(true) + return hit + } + _setInResponseCache(url, res) { + this._httpGetResponseCache[url] = res + return this + } + async httpGetUrlFromCache(url, queryStringMap = {}, responseClass = WillowHTTPResponse) { + const cacheKey = url + JSON.stringify(queryStringMap) + const cacheHit = this._getFromResponseCache(cacheKey) + if (!cacheHit) { + const res = await this.httpGetUrl(url, queryStringMap, responseClass) + this._setInResponseCache(cacheKey, res) + return res + } + return cacheHit + } + async httpGetUrlFromProxyCache(url) { + const queryStringMap = {} + queryStringMap.url = url + queryStringMap.cacheOnServer = "true" + return await this.httpGetUrlFromCache("/proxy", queryStringMap, WillowHTTPProxyCacheResponse) + } + async httpPostUrl(url, data) { + if (this._offlineMode) return new WillowHTTPResponse() + const superAgentResponse = await superagent + .post(this._makeRelativeUrlAbsolute(url)) + .set(this._headers || {}) + .send(data) + return new WillowHTTPResponse(superAgentResponse) + } + encodeURIComponent(str) { + return encodeURIComponent(str) + } + downloadFile(data, filename, filetype) { + // noop + } + async appendScript(url) {} + getWindowTitle() { + // todo: deep getParticleByBase/withBase/type/word or something? + const particles = this.topDownArray + const titleParticle = particles.find(particle => particle.firstWord === WillowConstants.titleTag) + return titleParticle ? titleParticle.content : "" + } + setWindowTitle(value) { + const particles = this.topDownArray + const headParticle = particles.find(particle => particle.firstWord === WillowConstants.tags.head) + headParticle.touchParticle(WillowConstants.titleTag).setContent(value) + return this + } + _getHostname() { + return this.location.hostname || "" + } + openUrl(link) { + // noop in willow + } + getPageHtml() { + return this.getHtmlStumpParticle().asHtmlWithSuids() + } + getStumpParticleFromElement(el) {} + setPasteHandler(fn) { + return this + } + setErrorHandler(fn) { + return this + } + setCopyHandler(fn) { + return this + } + setCutHandler(fn) { + return this + } + setResizeEndHandler(fn) { + return this + } + async confirmThen(message) { + return true + } + async promptThen(message, value) { + return value + } + setLoadedDroppedFileHandler(callback, helpText = "") {} + getWindowSize() { + return { + width: 1111, + height: 1111 + } + } + getDocumentSize() { + return this.getWindowSize() + } + isExternalLink(link) { + if (link && link.substr(0, 1) === "/") return false + if (!link.includes("//")) return false + const hostname = this._getHostname() + const url = new URL(link) + return url.hostname && hostname !== url.hostname + } + forceRepaint() {} + blurFocusedInput() {} +} +class WillowBrowser extends AbstractWillowBrowser { + constructor(fullHtmlPageUrlIncludingProtocolAndFileName) { + super(fullHtmlPageUrlIncludingProtocolAndFileName) + this._offlineMode = true + } +} +WillowBrowser._stumpsOnPage = 0 +class WillowBrowserShadow extends AbstractWillowShadow { + get element() { + if (!this._cachedEl) this._cachedEl = document.querySelector(`[${WillowConstants.uidAttribute}="${this.getShadowStumpParticle()._getUid()}"]`) + return this._cachedEl + } + getShadowValueFromAttr() { + return this.element.getAttribute(WillowConstants.value) + } + isShadowChecked() { + return this.element.checked + } + getShadowAttr(name) { + return this.element.getAttribute(name) + } + _logMessage(type) { + if (true) return true + WillowBrowserShadow._shadowUpdateNumber++ + console.log(`DOM Update ${WillowBrowserShadow._shadowUpdateNumber}: ${type}`) + } + // BEGIN MUTABLE METHODS: + // todo: add tests + // todo: idea, don't "paint" wall (dont append it to parent, until done.) + insertHtmlParticle(childStumpParticle, index) { + const { domElement } = childStumpParticle + const { element } = this + // todo: can we virtualize this? + // would it be a "virtual shadow?" + if (index === undefined) element.appendChild(domElement) + else if (index === 0) element.prepend(domElement) + else element.insertBefore(domElement, element.children[index]) + WillowBrowser._stumpsOnPage++ + this._logMessage("insert") + } + removeShadow() { + this.element.remove() + WillowBrowser._stumpsOnPage-- + this._logMessage("remove") + return this + } + setInputOrTextAreaValue(value) { + this.element.value = value + this._logMessage("val") + return this + } + setShadowAttr(name, value) { + this.element.setAttribute(name, value) + this._logMessage("attr") + return this + } + getShadowCss(prop) { + const { element } = this + const compStyles = window.getComputedStyle(element) + return compStyles.getPropertyValue(prop) + } + getShadowPosition() { + return this.element.getBoundingClientRect() + } + shadowHasClass(name) { + return this.element.classList.contains(name) + } + getShadowValue() { + // todo: cleanup, add tests + if (this.getShadowStumpParticle().isInputType()) return this.element.value + return this.element.value || this.getShadowValueFromAttr() + } + addClassToShadow(className) { + this.element.classList.add(className) + this._logMessage("addClass") + return this + } + removeClassFromShadow(className) { + this.element.classList.remove(className) + this._logMessage("removeClass") + return this + } + toggleShadow() { + const { element } = this + element.style.display = element.style.display == "none" ? "block" : "none" + this._logMessage("toggle") + return this + } + getShadowOuterHeight() { + return this.element.outerHeight + } + getShadowOuterWidth() { + return this.element.outerWidth + } + getShadowWidth() { + return this.element.innerWidth + } + getShadowHeight() { + return this.element.innerHeight + } + getShadowOffset() { + const element = this.element + if (!element.getClientRects().length) return { top: 0, left: 0 } + const rect = element.getBoundingClientRect() + const win = element.ownerDocument.defaultView + return { + top: rect.top + win.pageYOffset, + left: rect.left + win.pageXOffset + } + } + triggerShadowEvent(event) { + this.element.dispatchEvent(new Event(event)) + this._logMessage("trigger") + return this + } + onShadowEvent(event, fn) { + this.element.addEventListener(event, fn) + this._logMessage("bind on") + return this + } + onShadowEventWithSelector(event, selector, fn) { + this.element.addEventListener(event, function (evt) { + let target = evt.target + while (target !== null) { + if (target.matches(selector)) { + fn(target, evt) + return + } + target = target.parentElement + } + }) + this._logMessage("bind on") + return this + } + offShadowEvent(event, fn) { + this.element.removeEventListener(event, fn) + this._logMessage("bind off") + return this + } +} +WillowBrowserShadow._shadowUpdateNumber = 0 // todo: what is this for, debugging perf? +// same thing, except with side effects. +class RealWillowBrowser extends AbstractWillowBrowser { + findStumpParticlesByShadowClass(className) { + const stumpParticles = [] + const els = document.getElementsByClassName(className) + for (let el of els) { + stumpParticles.push(this.getStumpParticleFromElement(this)) + } + return stumpParticles + } + getElementById(id) { + return document.getElementById(id) + } + setHtmlOfElementWithIdHack(id, html = "") { + document.getElementById(id).innerHTML = html + } + setHtmlOfElementsWithClassHack(className, html = "") { + const els = document.getElementsByClassName(className) + for (let el of els) { + el.innerHTML = html + } + } + setValueOfElementWithIdHack(id, value = "") { + const el = document.getElementById(id) + el.value = value + } + setValueOfElementsWithClassHack(className, value = "") { + const els = document.getElementsByClassName(className) + for (let el of els) { + el.value = value + } + } + getElementByTagName(tagName) { + return document.getElementsByTagName(tagName)[0] + } + addSuidsToHtmlHeadAndBodyShadows() { + this.getElementByTagName(WillowConstants.tags.html).setAttribute(WillowConstants.uidAttribute, this.getHtmlStumpParticle()._getUid()) + this.getElementByTagName(WillowConstants.tags.head).setAttribute(WillowConstants.uidAttribute, this.getHeadStumpParticle()._getUid()) + this.getElementByTagName(WillowConstants.tags.body).setAttribute(WillowConstants.uidAttribute, this.getBodyStumpParticle()._getUid()) + } + getShadowClass() { + return WillowBrowserShadow + } + setCopyHandler(fn) { + document.addEventListener(BrowserEvents.copy, event => { + fn(event) + }) + return this + } + setCutHandler(fn) { + document.addEventListener(BrowserEvents.cut, event => { + fn(event) + }) + return this + } + setPasteHandler(fn) { + window.addEventListener(BrowserEvents.paste, fn, false) + return this + } + setErrorHandler(fn) { + window.addEventListener("error", fn) + window.addEventListener("unhandledrejection", fn) + return this + } + toggleFullScreen() { + const doc = document + if ((doc.fullScreenElement && doc.fullScreenElement !== null) || (!doc.mozFullScreen && !doc.webkitIsFullScreen)) { + if (doc.documentElement.requestFullScreen) doc.documentElement.requestFullScreen() + else if (doc.documentElement.mozRequestFullScreen) doc.documentElement.mozRequestFullScreen() + else if (doc.documentElement.webkitRequestFullScreen) doc.documentElement.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT) + } else { + if (doc.cancelFullScreen) doc.cancelFullScreen() + else if (doc.mozCancelFullScreen) doc.mozCancelFullScreen() + else if (doc.webkitCancelFullScreen) doc.webkitCancelFullScreen() + } + } + setCopyData(evt, str) { + const originalEvent = evt.originalEvent + originalEvent.preventDefault() + originalEvent.clipboardData.setData("text/plain", str) + originalEvent.clipboardData.setData("text/html", str) + } + getMousetrap() { + return window.Mousetrap + } + copyTextToClipboard(text) { + // http://stackoverflow.com/questions/400212/how-do-i-copy-to-the-clipboard-in-javascript + const textArea = document.createElement("textarea") + textArea.style.position = "fixed" + textArea.style.top = "0" + textArea.style.left = "0" + textArea.style.width = "2em" + textArea.style.height = "2em" + textArea.style.padding = "0" + textArea.style.border = "none" + textArea.style.outline = "none" + textArea.style.boxShadow = "none" + textArea.style.background = "transparent" + textArea.value = text + document.body.appendChild(textArea) + textArea.select() + try { + const successful = document.execCommand("copy") + } catch (err) {} + document.body.removeChild(textArea) + } + getStore() { + return window.store + } + getHash() { + return location.hash || "" + } + setHash(value) { + location.hash = value + } + getHost() { + return location.host + } + _getHostname() { + return location.hostname + } + async appendScript(url) { + if (!url) return undefined + if (!this._loadingPromises) this._loadingPromises = {} + if (this._loadingPromises[url]) return this._loadingPromises[url] + if (this.isNodeJs()) return undefined + this._loadingPromises[url] = this._appendScript(url) + return this._loadingPromises[url] + } + _appendScript(url) { + //https://bradb.net/blog/promise-based-js-script-loader/ + return new Promise(function (resolve, reject) { + let resolved = false + const scriptEl = document.createElement("script") + scriptEl.type = "text/javascript" + scriptEl.src = url + scriptEl.async = true + scriptEl.onload = scriptEl.onreadystatechange = function () { + if (!resolved && (!this.readyState || this.readyState == "complete")) { + resolved = true + resolve(this) + } + } + scriptEl.onerror = scriptEl.onabort = reject + document.head.appendChild(scriptEl) + }) + } + downloadFile(data, filename, filetype) { + const downloadLink = document.createElement("a") + downloadLink.setAttribute("href", `data:${filetype},` + encodeURIComponent(data)) + downloadLink.setAttribute("download", filename) + downloadLink.click() + } + reload() { + window.location.reload() + } + openUrl(link) { + window.open(link) + } + setResizeEndHandler(fn) { + let resizeTimer + window.addEventListener(BrowserEvents.resize, evt => { + const target = evt.target + if (target !== window) return // dont resize on div resizes + clearTimeout(resizeTimer) + resizeTimer = setTimeout(() => { + fn(this.getWindowSize()) + }, 100) + }) + return this + } + getStumpParticleFromElement(el) { + return this.getHtmlStumpParticle().getParticleByGuid(parseInt(el.getAttribute(WillowConstants.uidAttribute))) + } + forceRepaint() { + // todo: + } + getBrowserHtml() { + return document.documentElement.outerHTML + } + async confirmThen(message) { + return confirm(message) + } + async promptThen(message, value) { + return prompt(message, value) + } + getWindowSize() { + return { + width: window.innerWidth, + height: window.innerHeight + } + } + // todo: denote the side effect + blurFocusedInput() { + // todo: test against browser. + document.activeElement.blur() + } + setLoadedDroppedFileHandler(callback, helpText = "") { + const bodyStumpParticle = this.getBodyStumpParticle() + const bodyShadow = bodyStumpParticle.getShadow() + // Added the below to ensure dragging from the chrome downloads bar works + // http://stackoverflow.com/questions/19526430/drag-and-drop-file-uploads-from-chrome-downloads-bar + const handleChromeBug = event => { + const originalEvent = event.originalEvent + const effect = originalEvent.dataTransfer.effectAllowed + originalEvent.dataTransfer.dropEffect = effect === "move" || effect === "linkMove" ? "move" : "copy" + } + const dragoverHandler = event => { + handleChromeBug(event) + event.preventDefault() + event.stopPropagation() + if (!bodyStumpParticle.stumpParticleHasClass("dragOver")) { + bodyStumpParticle.insertChildParticle(`div ${helpText} + id dragOverHelp`) + bodyStumpParticle.addClassToStumpParticle("dragOver") + // Add the help, and then hopefull we'll get a dragover event on the dragOverHelp, then + // 50ms later, add the dragleave handler, and from now on drag leave will only happen on the help + // div + setTimeout(function () { + bodyShadow.onShadowEvent(BrowserEvents.dragleave, dragleaveHandler) + }, 50) + } + } + const dragleaveHandler = event => { + event.preventDefault() + event.stopPropagation() + bodyStumpParticle.removeClassFromStumpParticle("dragOver") + bodyStumpParticle.findStumpParticleByChild("id dragOverHelp").removeStumpParticle() + bodyShadow.offShadowEvent(BrowserEvents.dragleave, dragleaveHandler) + } + const dropHandler = async event => { + event.preventDefault() + event.stopPropagation() + bodyStumpParticle.removeClassFromStumpParticle("dragOver") + bodyStumpParticle.findStumpParticleByChild("id dragOverHelp").removeStumpParticle() + const droppedItems = event.originalEvent.dataTransfer.items + // NOTE: YOU NEED TO STAY IN THE "DROP" EVENT, OTHERWISE THE DATATRANSFERITEMS MUTATE + // (BY DESIGN) https://bugs.chromium.org/p/chromium/issues/detail?id=137231 + // DO NOT ADD AN AWAIT IN THIS LOOP. IT WILL BREAK. + const items = [] + for (let droppedItem of droppedItems) { + const entry = droppedItem.webkitGetAsEntry() + items.push(this._handleDroppedEntry(entry)) + } + const results = await Promise.all(items) + callback(results) + } + bodyShadow.onShadowEvent(BrowserEvents.dragover, dragoverHandler) + bodyShadow.onShadowEvent(BrowserEvents.drop, dropHandler) + // todo: why do we do this? + bodyShadow.onShadowEvent(BrowserEvents.dragenter, function (event) { + event.preventDefault() + event.stopPropagation() + }) + } + _handleDroppedEntry(item, path = "") { + // http://stackoverflow.com/questions/3590058/does-html5-allow-drag-drop-upload-of-folders-or-a-folder-tree + // http://stackoverflow.com/questions/6756583/prevent-browser-from-loading-a-drag-and-dropped-file + return item.isFile ? this._handleDroppedFile(item) : this._handleDroppedDirectory(item, path) + } + _handleDroppedDirectory(item, path) { + return new Promise((resolve, reject) => { + item.createReader().readEntries(async entries => { + const promises = [] + for (let i = 0; i < entries.length; i++) { + promises.push(this._handleDroppedEntry(entries[i], path + item.name + "/")) + } + const res = await Promise.all(promises) + resolve(res) + }) + }) + } + _handleDroppedFile(file) { + // https://developer.mozilla.org/en-US/docs/Using_files_from_web_applications + // http://www.sitepoint.com/html5-javascript-open-dropped-files/ + return new Promise((resolve, reject) => { + file.file(data => { + const reader = new FileReader() + reader.onload = evt => { + resolve({ data: evt.target.result, filename: data.name }) + } + reader.onerror = err => reject(err) + reader.readAsText(data) + }) + }) + } + _getFocusedShadow() { + const stumpParticle = this.getStumpParticleFromElement(document.activeElement) + return stumpParticle && stumpParticle.getShadow() + } +} +class AbstractTheme { + hakonToCss(str) { + const hakonProgram = new hakonParser(str) + // console.log(hakonProgram.getAllErrors()) + return hakonProgram.compile() + } +} +class DefaultTheme extends AbstractTheme {} +class AbstractParticleComponentParser extends ParserBackedParticle { + async startWhenReady() { + if (this.isNodeJs()) return this.start() + document.addEventListener( + "DOMContentLoaded", + async () => { + this.start() + }, + false + ) + } + start() { + this._bindParticleComponentFrameworkCommandListenersOnBody() + this.renderAndGetRenderReport(this.willowBrowser.getBodyStumpParticle()) + } + get willowBrowser() { + if (!this._willowBrowser) { + if (this.isNodeJs()) { + this._willowBrowser = new WillowBrowser("http://localhost:8000/index.html") + } else { + this._willowBrowser = new RealWillowBrowser(window.location.href) + } + } + return this._willowBrowser + } + onCommandError(err) { + throw err + } + _setMouseEvent(evt) { + this._mouseEvent = evt + return this + } + getMouseEvent() { + return this._mouseEvent || this.willowBrowser.getMockMouseEvent() + } + _onCommandWillRun() { + // todo: remove. currently used by ohayo + } + _getCommandArgumentsFromStumpParticle(stumpParticle, commandMethod) { + if (commandMethod.includes(" ")) { + // todo: cleanup and document + // It seems the command arguments can from the method string or from form values. + const parts = commandMethod.split(" ") + return { + uno: parts[1], + dos: parts[2] + } + } + const shadow = stumpParticle.getShadow() + let valueParam + if (stumpParticle.isStumpParticleCheckbox()) valueParam = shadow.isShadowChecked() ? true : false + // todo: fix bug if nothing is entered. + else if (shadow.getShadowValue() !== undefined) valueParam = shadow.getShadowValue() + else valueParam = stumpParticle.getStumpParticleAttr("value") + const nameParam = stumpParticle.getStumpParticleAttr("name") + return { + uno: valueParam, + dos: nameParam + } + } + getStumpParticleString() { + return this.willowBrowser.getHtmlStumpParticle().toString() + } + _getHtmlOnlyParticles() { + const particles = [] + this.willowBrowser.getHtmlStumpParticle().deepVisit(particle => { + if (particle.firstWord === "styleTag" || (particle.content || "").startsWith(" { + if (particle.firstWord === "styleTag" || (particle.content || "").startsWith(" particle.getTextContent()) + .filter(text => text) + .join("\n") + } + getCommandNames() { + return Object.getOwnPropertyNames(Object.getPrototypeOf(this)).filter(word => word.endsWith("Command")) + } + async _executeCommandOnStumpParticle(stumpParticle, commandMethod) { + const params = this._getCommandArgumentsFromStumpParticle(stumpParticle, commandMethod) + if (commandMethod.includes(" ")) + // todo: cleanup + commandMethod = commandMethod.split(" ")[0] + this.addToCommandLog([commandMethod, params.uno, params.dos].filter(identity => identity).join(" ")) + this._onCommandWillRun() // todo: remove. currently used by ohayo + let particleComponent = stumpParticle.getStumpParticleParticleComponent() + while (!particleComponent[commandMethod]) { + const parent = particleComponent.parent + if (parent === particleComponent) throw new Error(`Unknown command "${commandMethod}"`) + if (!parent) debugger + particleComponent = parent + } + try { + await particleComponent[commandMethod](params.uno, params.dos) + } catch (err) { + this.onCommandError(err) + } + } + _bindParticleComponentFrameworkCommandListenersOnBody() { + const willowBrowser = this.willowBrowser + const bodyShadow = willowBrowser.getBodyStumpParticle().getShadow() + const app = this + const checkAndExecute = (el, attr, evt) => { + const stumpParticle = willowBrowser.getStumpParticleFromElement(el) + evt.preventDefault() + evt.stopImmediatePropagation() + this._executeCommandOnStumpParticle(stumpParticle, stumpParticle.getStumpParticleAttr(attr)) + return false + } + bodyShadow.onShadowEventWithSelector(BrowserEvents.contextmenu, `[${WillowConstants.contextMenuCommand}]`, function (target, evt) { + if (evt.ctrlKey) return true + app._setMouseEvent(evt) // todo: remove? + return checkAndExecute(target, WillowConstants.contextMenuCommand, evt) + }) + bodyShadow.onShadowEventWithSelector(BrowserEvents.click, `[${WillowConstants.clickCommand}]`, function (target, evt) { + if (evt.shiftKey) return checkAndExecute(this, WillowConstants.shiftClickCommand, evt) + app._setMouseEvent(evt) // todo: remove? + return checkAndExecute(target, WillowConstants.clickCommand, evt) + }) + bodyShadow.onShadowEventWithSelector(BrowserEvents.dblclick, `[${WillowConstants.doubleClickCommand}]`, function (target, evt) { + if (evt.target !== evt.currentTarget) return true // direct dblclicks only + app._setMouseEvent(evt) // todo: remove? + return checkAndExecute(target, WillowConstants.doubleClickCommand, evt) + }) + bodyShadow.onShadowEventWithSelector(BrowserEvents.blur, `[${WillowConstants.blurCommand}]`, function (target, evt) { + return checkAndExecute(target, WillowConstants.blurCommand, evt) + }) + bodyShadow.onShadowEventWithSelector(BrowserEvents.keyup, `[${WillowConstants.keyUpCommand}]`, function (target, evt) { + return checkAndExecute(target, WillowConstants.keyUpCommand, evt) + }) + bodyShadow.onShadowEventWithSelector(BrowserEvents.change, `[${WillowConstants.changeCommand}]`, function (target, evt) { + return checkAndExecute(target, WillowConstants.changeCommand, evt) + }) + } + stopPropagationCommand() { + // todo: remove? + // intentional noop + } + // todo: remove? + async clearMessageBufferCommand() { + delete this._messageBuffer + } + // todo: remove? + async unmountAndDestroyCommand() { + this.unmountAndDestroy() + } + toggleParticleComponentFrameworkDebuggerCommand() { + // todo: move somewhere else? + // todo: cleanup + const app = this.root + const particle = app.getParticle("ParticleComponentFrameworkDebuggerComponent") + if (particle) { + particle.unmountAndDestroy() + } else { + app.appendLine("ParticleComponentFrameworkDebuggerComponent") + app.renderAndGetRenderReport() + } + } + getStumpParticle() { + return this._htmlStumpParticle + } + toHakonCode() { + return "" + } + getTheme() { + if (!this.isRoot()) return this.root.getTheme() + if (!this._theme) this._theme = new DefaultTheme() + return this._theme + } + getCommandsBuffer() { + if (!this._commandsBuffer) this._commandsBuffer = [] + return this._commandsBuffer + } + addToCommandLog(command) { + this.getCommandsBuffer().push({ + command: command, + time: this._getProcessTimeInMilliseconds() + }) + } + getMessageBuffer() { + if (!this._messageBuffer) this._messageBuffer = new Particle() + return this._messageBuffer + } + // todo: move this to particle class? or other higher level class? + addStumpCodeMessageToLog(message) { + // note: we have 1 parameter, and are going to do type inference first. + // Todo: add actions that can be taken from a message? + // todo: add tests + this.getMessageBuffer().appendLineAndChildren("message", message) + } + addStumpErrorMessageToLog(errorMessage) { + // todo: cleanup! + return this.addStumpCodeMessageToLog(`div + class OhayoError + bern${Particle.nest(errorMessage, 2)}`) + } + logMessageText(message = "") { + const pre = `pre + bern${Particle.nest(message, 2)}` + return this.addStumpCodeMessageToLog(pre) + } + unmount() { + if ( + !this.isMounted() // todo: why do we need this check? + ) + return undefined + this._getChildParticleComponents().forEach(child => child.unmount()) + this.particleComponentWillUnmount() + this._removeCss() + this._removeHtml() + delete this._lastRenderedTime + this.particleComponentDidUnmount() + } + _removeHtml() { + this._htmlStumpParticle.removeStumpParticle() + delete this._htmlStumpParticle + } + toStumpCode() { + return `div + class ${this.getCssClassNames().join(" ")}` + } + getCssClassNames() { + return this._getJavascriptPrototypeChainUpTo("AbstractParticleComponentParser") + } + particleComponentWillMount() {} + async particleComponentDidMount() { + AbstractParticleComponentParser._mountedParticleComponents++ + } + particleComponentDidUnmount() { + AbstractParticleComponentParser._mountedParticleComponents-- + } + particleComponentWillUnmount() {} + getNewestTimeToRender() { + return this._lastTimeToRender + } + _setLastRenderedTime(time) { + this._lastRenderedTime = time + return this + } + async particleComponentDidUpdate() {} + _getChildParticleComponents() { + return this.getChildrenByParser(AbstractParticleComponentParser) + } + _hasChildrenParticleComponents() { + return this._getChildParticleComponents().length > 0 + } + // todo: this is hacky. we do it so we can just mount all tiles to wall. + getStumpParticleForChildren() { + return this.getStumpParticle() + } + _getLastRenderedTime() { + return this._lastRenderedTime + } + get _css() { + return this.getTheme().hakonToCss(this.toHakonCode()) + } + toPlainHtml(containerId) { + return `
+ +${new stumpParser(this.toStumpCode()).compile()} +
` + } + _updateAndGetUpdateReport() { + const reasonForUpdatingOrNot = this.getWhetherToUpdateAndReason() + if (!reasonForUpdatingOrNot.shouldUpdate) return reasonForUpdatingOrNot + this._setLastRenderedTime(this._getProcessTimeInMilliseconds()) + this._removeCss() + this._mountCss() + // todo: fucking switch to react? looks like we don't update parent because we dont want to nuke children. + // okay. i see why we might do that for non tile particleComponents. but for Tile particleComponents, seems like we arent nesting, so why not? + // for now + if (this._hasChildrenParticleComponents()) return { shouldUpdate: false, reason: "did not update because is a parent" } + this._updateHtml() + this._lastTimeToRender = this._getProcessTimeInMilliseconds() - this._getLastRenderedTime() + return reasonForUpdatingOrNot + } + _updateHtml() { + const stumpParticleToMountOn = this._htmlStumpParticle.parent + const currentIndex = this._htmlStumpParticle.getIndex() + this._removeHtml() + this._mountHtml(stumpParticleToMountOn, this._toLoadedOrLoadingStumpCode(), currentIndex) + } + unmountAndDestroy() { + this.unmount() + return this.destroy() + } + // todo: move to keyword particle class? + toggle(firstWord, contentOptions) { + const currentParticle = this.getParticle(firstWord) + if (!contentOptions) return currentParticle ? currentParticle.unmountAndDestroy() : this.appendLine(firstWord) + const currentContent = currentParticle === undefined ? undefined : currentParticle.content + const index = contentOptions.indexOf(currentContent) + const newContent = index === -1 || index + 1 === contentOptions.length ? contentOptions[0] : contentOptions[index + 1] + this.delete(firstWord) + if (newContent) this.touchParticle(firstWord).setContent(newContent) + return newContent + } + isMounted() { + return !!this._htmlStumpParticle + } + toggleAndRender(firstWord, contentOptions) { + this.toggle(firstWord, contentOptions) + this.root.renderAndGetRenderReport() + } + _getFirstOutdatedDependency(lastRenderedTime = this._getLastRenderedTime() || 0) { + return this.getDependencies().find(dep => dep.getLineModifiedTime() > lastRenderedTime) + } + getWhetherToUpdateAndReason() { + const mTime = this.getLineModifiedTime() + const lastRenderedTime = this._getLastRenderedTime() || 0 + const staleTime = mTime - lastRenderedTime + if (lastRenderedTime === 0) + return { + shouldUpdate: true, + reason: "shouldUpdate because this ParticleComponent hasn't been rendered yet", + staleTime: staleTime + } + if (staleTime > 0) + return { + shouldUpdate: true, + reason: "shouldUpdate because this ParticleComponent changed", + staleTime: staleTime + } + const outdatedDependency = this._getFirstOutdatedDependency(lastRenderedTime) + if (outdatedDependency) + return { + shouldUpdate: true, + reason: "Should update because a dependency updated", + dependency: outdatedDependency, + staleTime: outdatedDependency.getLineModifiedTime() - lastRenderedTime + } + return { + shouldUpdate: false, + reason: "Should NOT update because no dependency changed", + lastRenderedTime: lastRenderedTime, + mTime: mTime + } + } + getDependencies() { + return [] + } + _getParticleComponentsThatNeedRendering(arr) { + this._getChildParticleComponents().forEach(child => { + const reasonForUpdatingOrNot = child.getWhetherToUpdateAndReason() + if (!child.isMounted() || reasonForUpdatingOrNot.shouldUpdate) arr.push({ child: child, childUpdateBecause: reasonForUpdatingOrNot }) + child._getParticleComponentsThatNeedRendering(arr) + }) + } + toStumpLoadingCode() { + return `div Loading ${this.firstWord}... + class ${this.getCssClassNames().join(" ")} + id ${this.getParticleComponentId()}` + } + getParticleComponentId() { + // html ids can't begin with a number + return "particleComponent" + this._getUid() + } + _toLoadedOrLoadingStumpCode() { + if (!this.isLoaded()) return this.toStumpLoadingCode() + this.setRunTimePhaseError("renderPhase") + try { + return this.toStumpCode() + } catch (err) { + console.error(err) + this.setRunTimePhaseError("renderPhase", err) + return this.toStumpErrorStateCode(err) + } + } + toStumpErrorStateCode(err) { + return `div ${err} + class ${this.getCssClassNames().join(" ")} + id ${this.getParticleComponentId()}` + } + _mount(stumpParticleToMountOn, index) { + this._setLastRenderedTime(this._getProcessTimeInMilliseconds()) + this.particleComponentWillMount() + this._mountCss() + this._mountHtml(stumpParticleToMountOn, this._toLoadedOrLoadingStumpCode(), index) // todo: add index back? + this._lastTimeToRender = this._getProcessTimeInMilliseconds() - this._getLastRenderedTime() + return this + } + // todo: we might be able to squeeze virtual dom in here on the mountCss and mountHtml methods. + _mountCss() { + const css = this._css + if (!css) return this + // todo: only insert css once per class? have a set? + this._cssStumpParticle = this._getPageHeadStump().insertCssChildParticle(`styleTag + for ${this.constructor.name} + bern${Particle.nest(css, 2)}`) + } + _getPageHeadStump() { + return this.root.willowBrowser.getHeadStumpParticle() + } + _removeCss() { + if (!this._cssStumpParticle) return this + this._cssStumpParticle.removeCssStumpParticle() + delete this._cssStumpParticle + } + _mountHtml(stumpParticleToMountOn, htmlCode, index) { + this._htmlStumpParticle = stumpParticleToMountOn.insertChildParticle(htmlCode, index) + this._htmlStumpParticle.setStumpParticleParticleComponent(this) + } + renderAndGetRenderReport(stumpParticle, index) { + const isUpdateOp = this.isMounted() + let particleComponentUpdateReport = { + shouldUpdate: false, + reason: "" + } + if (isUpdateOp) particleComponentUpdateReport = this._updateAndGetUpdateReport() + else this._mount(stumpParticle, index) + const stumpParticleForChildren = this.getStumpParticleForChildren() + // Todo: insert delayed rendering? + const childResults = this._getChildParticleComponents().map((child, index) => child.renderAndGetRenderReport(stumpParticleForChildren, index)) + if (isUpdateOp) { + if (particleComponentUpdateReport.shouldUpdate) { + try { + if (this.isLoaded()) this.particleComponentDidUpdate() + } catch (err) { + console.error(err) + } + } + } else { + try { + if (this.isLoaded()) this.particleComponentDidMount() + } catch (err) { + console.error(err) + } + } + let str = `${this.getWord(0) || this.constructor.name} ${isUpdateOp ? "update" : "mount"} ${particleComponentUpdateReport.shouldUpdate} ${particleComponentUpdateReport.reason}` + childResults.forEach(child => (str += "\n" + child.toString(1))) + return new Particle(str) + } +} +AbstractParticleComponentParser._mountedParticleComponents = 0 +class ParticleComponentFrameworkDebuggerComponent extends AbstractParticleComponentParser { + toHakonCode() { + return `.ParticleComponentFrameworkDebuggerComponent + position fixed + top 5px + left 5px + z-index 1000 + background rgba(254,255,156, .95) + box-shadow 1px 1px 1px rgba(0,0,0,.5) + padding 12px + overflow scroll + max-height 500px +.ParticleComponentFrameworkDebuggerComponentCloseButton + position absolute + cursor pointer + opacity .9 + top 2px + right 2px + &:hover + opacity 1` + } + toStumpCode() { + const app = this.root + return `div + class ParticleComponentFrameworkDebuggerComponent + div x + class ParticleComponentFrameworkDebuggerComponentCloseButton + clickCommand toggleParticleComponentFrameworkDebuggerCommand + div + span This app is powered by the + a ParticleComponentFramework + href https://github.com/breck7/scrollsdk/tree/main/particleComponentFramework + p ${app.numberOfLines} components loaded. ${WillowBrowser._stumpsOnPage} stumps on page. + pre + bern +${app.toString(3)}` + } +} +class AbstractGithubTriangleComponent extends AbstractParticleComponentParser { + constructor() { + super(...arguments) + this.githubLink = `https://github.com/breck7/scrollsdk` + } + toHakonCode() { + return `.AbstractGithubTriangleComponent + display block + position absolute + top 0 + right 0` + } + toStumpCode() { + return `a + class AbstractGithubTriangleComponent + href ${this.githubLink} + img + src ../images/github-fork.svg` + } +} +window.AbstractGithubTriangleComponent = AbstractGithubTriangleComponent +window.AbstractParticleComponentParser = AbstractParticleComponentParser +window.WillowBrowser = WillowBrowser +window.ParticleComponentFrameworkDebuggerComponent = ParticleComponentFrameworkDebuggerComponent diff --git a/search.html b/search.html index e34e424a02..706036fab1 100644 --- a/search.html +++ b/search.html @@ -3,12 +3,12 @@ Search CancerDB - + - +