@@ -3950,7 +3950,7 @@ define("LiveDevelopment/LiveDevMultiBrowser", function (require, exports, module
39503950 * @param {Document} doc
39513951 */
39523952 function _onDirtyFlagChange(event, doc) {
3953- if (!isActive() || !_server || !_liveDocument) {
3953+ if (!isActive() || !_server || !_liveDocument || !_liveDocument.isRelated ) {
39543954 return;
39553955 }
39563956
@@ -15704,6 +15704,12 @@ define("document/DocumentCommandHandlers", function (require, exports, module) {
1570415704 _$dirtydot = $(".dirty-dot", _$titleWrapper);
1570515705 });
1570615706
15707+ if(Phoenix.isSpecRunnerWindow){
15708+ _$titleContainerToolbar = $("#titlebar");
15709+ _$titleWrapper = $(".title-wrapper");
15710+ _$title = $(".title");
15711+ _$dirtydot = $(".dirty-dot");
15712+ }
1570715713
1570815714 let firstProjectOpenHandled = false;
1570915715 ProjectManager.on(ProjectManager.EVENT_AFTER_PROJECT_OPEN, ()=>{
@@ -25151,7 +25157,7 @@ define("editor/EditorStatusBar", function (require, exports, module) {
2515125157 fullPath = document.file.fullPath;
2515225158
2515325159 var fileType = (document.file instanceof InMemoryFile) ? "newFile" : "existingFile",
25154- filelanguageName = lang ? lang._name : "";
25160+ filelanguageName = lang ? lang._name||"" : "";
2515525161
2515625162 Metrics.countEvent(
2515725163 Metrics.EVENT_TYPE.EDITOR,
@@ -48886,6 +48892,7 @@ define("help/HelpCommandHandlers", function (require, exports, module) {
4888648892 */
4888748893
4888848894/*jslint regexp: true */
48895+ /*global jsPromise*/
4888948896
4889048897/**
4889148898 * Set of utilities for simple parsing of CSS text.
@@ -48896,11 +48903,13 @@ define("language/CSSUtils", function (require, exports, module) {
4889648903 var CodeMirror = require("thirdparty/CodeMirror/lib/codemirror"),
4889748904 Async = require("utils/Async"),
4889848905 DocumentManager = require("document/DocumentManager"),
48906+ AppInit = require("utils/AppInit"),
4889948907 EditorManager = require("editor/EditorManager"),
4890048908 HTMLUtils = require("language/HTMLUtils"),
4890148909 LanguageManager = require("language/LanguageManager"),
4890248910 ProjectManager = require("project/ProjectManager"),
4890348911 TokenUtils = require("utils/TokenUtils"),
48912+ IndexingWorker = require("worker/IndexingWorker"),
4890448913 _ = require("thirdparty/lodash");
4890548914
4890648915 // Constants
@@ -50673,6 +50682,208 @@ define("language/CSSUtils", function (require, exports, module) {
5067350682 return (allSelectors.length ? allSelectors[0].selectorGroup || allSelectors[0].selector : "");
5067450683 }
5067550684
50685+ function _extractSelectorSet(selectorList) {
50686+ const regex = /[{}!]/;
50687+ const selectors = new Set();
50688+ if(!selectorList){
50689+ return selectors;
50690+ }
50691+ for(let item of selectorList) {
50692+ if(regex.test(item) || !item.trim()){
50693+ // this happens for scss selectors like #${var}-something. we ignore that for now instead of resolving
50694+ continue;
50695+ }
50696+ const extracted = extractSelectorBase(item);
50697+ extracted.trim() && selectors.add(extracted); // x:hover or x::some -> x
50698+ }
50699+ return selectors;
50700+ }
50701+
50702+ const CSSSelectorCache = new Map();
50703+
50704+ function _projectFileChanged(_evt, entry) {
50705+ if(!entry){
50706+ return;
50707+ }
50708+ let changedPath = entry.fullPath;
50709+ if(entry.isFile) {
50710+ CSSSelectorCache.delete(changedPath);
50711+ } else if(entry.isDirectory) {
50712+ changedPath = Phoenix.VFS.ensureTrailingSlash(changedPath);
50713+ const cachedFilePaths = Array.from(CSSSelectorCache.keys());
50714+ for(let filePath of cachedFilePaths) {
50715+ if(filePath.startsWith(changedPath)){
50716+ CSSSelectorCache.delete(filePath);
50717+ }
50718+ }
50719+ }
50720+ }
50721+
50722+ const MODE_MAP = {
50723+ css: "CSS",
50724+ less: "LESS",
50725+ scss: "SCSS"
50726+ };
50727+
50728+ function _loadFileAndScanCSSSelectorCached(fullPath) {
50729+ return new Promise(resolve=>{
50730+ DocumentManager.getDocumentForPath(fullPath)
50731+ .done(function (doc) {
50732+ // Find all matching rules for the given CSS file's content, and add them to the
50733+ // overall search result
50734+ let selectors = new Set();
50735+ const cachedSelectors = CSSSelectorCache.get(fullPath);
50736+ if(cachedSelectors){
50737+ resolve(cachedSelectors);
50738+ return;
50739+ }
50740+ const langID = doc.getLanguage().getId();
50741+ if(!MODE_MAP[langID]){
50742+ console.log("Cannot parse CSS for mode :", langID, "ignoring", fullPath);
50743+ resolve(selectors);
50744+ return;
50745+ }
50746+ console.log("scanning file for css selector collation: ", fullPath);
50747+ IndexingWorker.execPeer("css_getAllSymbols",
50748+ {text: doc.getText(), cssMode: "CSS", filePath: fullPath})
50749+ .then((selectorArray)=>{
50750+ selectors = _extractSelectorSet(selectorArray);
50751+ CSSSelectorCache.set(fullPath, selectors);
50752+ resolve(selectors);
50753+ }).catch(err=>{
50754+ console.warn("CSS language service unable to get selectors for" + fullPath, err);
50755+ resolve(selectors); // still resolve, so the overall result doesn't reject
50756+ });
50757+ })
50758+ .fail(function (error) {
50759+ console.warn("Unable to read " + fullPath + " during CSS selector search:", error);
50760+ resolve(new Set()); // still resolve, so the overall result doesn't reject
50761+ });
50762+ });
50763+ }
50764+
50765+ function extractSelectorBase(selector) {
50766+ // Use a regular expression to find the base part of the selector before any spaces, combinators,
50767+ // pseudo-classes, or attribute selectors
50768+ const match = selector.match(/^[^\s>+~:\[]+/);
50769+ // Return the match if found, otherwise return the original selector if no ':' or '::' is present
50770+ selector = match ? match[0] : selector;
50771+ if(selector.startsWith(".")) {
50772+ // Eg .class1.class2 type selector, we have to consider this too, so we always take the first segment only
50773+ selector = "." + selector.split(".")[1];
50774+ }
50775+ return selector;
50776+ }
50777+
50778+ const _htmlLikeFileExts = ["htm", "html", "xhtml", "php"];
50779+ function _isHtmlLike(editor) {
50780+ const fullPath = editor && editor.document.file.fullPath;
50781+ if (!editor || !LanguageManager.getLanguageForPath(fullPath)) {
50782+ return false;
50783+ }
50784+
50785+ return (_htmlLikeFileExts.indexOf(LanguageManager.getLanguageForPath(fullPath).getId() || "") !== -1);
50786+ }
50787+
50788+ function _getAllSelectorsInCurrentHTMLEditor() {
50789+ return new Promise(resolve=>{
50790+ let selectors = new Set();
50791+ const htmlEditor = EditorManager.getCurrentFullEditor();
50792+ if (!htmlEditor || !_isHtmlLike(htmlEditor) ) {
50793+ resolve(selectors);
50794+ return;
50795+ }
50796+
50797+ // Find all <style> blocks in the HTML file
50798+ const styleBlocks = HTMLUtils.findStyleBlocks(htmlEditor);
50799+ let cssText = "";
50800+
50801+ styleBlocks.forEach(function (styleBlockInfo) {
50802+ // Search this one <style> block's content
50803+ cssText += styleBlockInfo.text;
50804+ });
50805+ const fullPath = htmlEditor.document.file.fullPath;
50806+ IndexingWorker.execPeer("css_getAllSymbols", {text: cssText, cssMode: "CSS", filePath: fullPath})
50807+ .then((selectorArray)=>{
50808+ selectors = _extractSelectorSet(selectorArray);
50809+ CSSSelectorCache.set(fullPath, selectors);
50810+ resolve(selectors);
50811+ }).catch(err=>{
50812+ console.warn("CSS language service unable to get selectors for" + fullPath, err);
50813+ resolve(selectors); // still resolve, so the overall result doesn't reject
50814+ });
50815+ });
50816+ }
50817+
50818+ let globalPrecacheRun = 0;
50819+ function getAllCssSelectorsInProject(options = {
50820+ includeClasses: true,
50821+ includeIDs: true,
50822+ scanCurrentHtml: true
50823+ }) {
50824+ globalPrecacheRun ++; // we need to stop the pre cache logic from doing the same cache again
50825+ return new Promise(resolve=>{
50826+ ProjectManager.getAllFiles(ProjectManager.getLanguageFilter(["css", "less", "scss"]))
50827+ .done(function (cssFiles) {
50828+ // Create an array of promises from the array of cssFiles
50829+ const promises = cssFiles.map(fileInfo => _loadFileAndScanCSSSelectorCached(fileInfo.fullPath));
50830+ if(options.scanCurrentHtml){
50831+ promises.push(_getAllSelectorsInCurrentHTMLEditor());
50832+ }
50833+ const mergedSets = new Set();
50834+ // Use Promise.allSettled to handle all promises
50835+ Promise.allSettled(promises)
50836+ .then(results => {
50837+ results.forEach((result, index) => {
50838+ if (result.status === 'fulfilled') {
50839+ result.value.forEach(value => {
50840+ if((options.includeClasses && value.startsWith(".")) ||
50841+ (options.includeIDs && value.startsWith("#"))) {
50842+ mergedSets.add(value);
50843+ }
50844+ });
50845+ } else {
50846+ console.error(`Error collect css selectors from file ${cssFiles[index].fullPath}:`,
50847+ result.reason);
50848+ }
50849+ });
50850+ resolve(Array.from(mergedSets.keys()));
50851+ });
50852+ });
50853+ });
50854+ }
50855+
50856+ async function _populateSelectorCache() {
50857+ globalPrecacheRun ++;
50858+ const currentCacheRun = globalPrecacheRun;
50859+ const cssFiles = await jsPromise(ProjectManager.getAllFiles(
50860+ ProjectManager.getLanguageFilter(["css", "less", "scss"])));
50861+ for(let cssFile of cssFiles){
50862+ if(currentCacheRun !== globalPrecacheRun) {
50863+ // this is for cases in very large projects where the project switches while a
50864+ // project wide css parse was in progress.
50865+ break;
50866+ }
50867+ await _loadFileAndScanCSSSelectorCached(cssFile.fullPath); // this is serial to not hog processor
50868+ }
50869+ }
50870+
50871+ AppInit.appReady(function () {
50872+ if(Phoenix.isSpecRunnerWindow){
50873+ // no unit tests event handlers
50874+ return;
50875+ }
50876+ ProjectManager.on(ProjectManager.EVENT_PROJECT_FILE_CHANGED, _projectFileChanged);
50877+ ProjectManager.on(ProjectManager.EVENT_PROJECT_OPEN, ()=>{
50878+ CSSSelectorCache.clear();
50879+ setTimeout(_populateSelectorCache, 2000);
50880+ });
50881+ setTimeout(_populateSelectorCache, 2000);
50882+ DocumentManager.on(DocumentManager.EVENT_DOCUMENT_CHANGE, function (event, doc, _changelist) {
50883+ CSSSelectorCache.delete(doc.file.fullPath);
50884+ });
50885+ });
50886+
5067650887 exports._findAllMatchingSelectorsInText = _findAllMatchingSelectorsInText; // For testing only
5067750888 exports.findMatchingRules = findMatchingRules;
5067850889 exports.extractAllSelectors = extractAllSelectors;
@@ -50683,6 +50894,7 @@ define("language/CSSUtils", function (require, exports, module) {
5068350894 exports.getRangeSelectors = getRangeSelectors;
5068450895 exports.getCompleteSelectors = getCompleteSelectors;
5068550896 exports.isCSSPreprocessorFile = isCSSPreprocessorFile;
50897+ exports.getAllCssSelectorsInProject = getAllCssSelectorsInProject;
5068650898
5068750899 exports.SELECTOR = SELECTOR;
5068850900 exports.PROP_NAME = PROP_NAME;
0 commit comments